{
  "project": "PwaSlotCash",
  "revision": "326ea0a335dbfa05d26845ce603d4bf9fb62e5ac",
  "scope": "read-only source comparison; no runtime performance benchmark; Stake capture is incomplete",
  "modules": [
    {
      "id": "CMP-A01",
      "module": "应用外壳与页面启动",
      "category": "架构",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/RootClientComponent.tsx",
          "line": 18,
          "end_line": 45,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "76b4abc04a9f851ee6f71c7ef198b20ea37fda5819c87934537bca837729682b"
        },
        {
          "path": "PwaSlotCash/src/app/Main.tsx",
          "line": 62,
          "end_line": 78,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "a0cc43a3cfb924da3b49a5568d6e27807ff7607fadf8f95251d83e5ff691d327"
        }
      ],
      "stake_ids": [
        "SUP02",
        "L09",
        "L12"
      ],
      "current": "RootClientComponent统一挂Redux Provider，普通路径加载ssr:false的Main；FAQ直接渲染children。Main负责risk、通知、会话及页面生命周期等初始化。",
      "difference": "PwaSlotCash显式区分FAQ预渲染与客户端业务Main；Stake由node_ids/data启动分层layout。此处只比较启动组织，不评测SSR或首屏性能。",
      "suggestion": "保留RootClientComponent中的静态页分支；新增仅展示内容的页面时明确是否真的依赖Main初始化。",
      "limits": [
        "源码和打包证据不能直接证明线上首屏耗时或SSR效果。"
      ],
      "raw": {
        "id": "CMP-A01",
        "module": "应用外壳与页面启动",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/app/RootClientComponent.tsx",
            "line": 18,
            "end_line": 45,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "76b4abc04a9f851ee6f71c7ef198b20ea37fda5819c87934537bca837729682b"
          },
          {
            "path": "PwaSlotCash/src/app/Main.tsx",
            "line": 62,
            "end_line": 78,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "a0cc43a3cfb924da3b49a5568d6e27807ff7607fadf8f95251d83e5ff691d327"
          }
        ],
        "stake_evidence_ids": [
          "SUP02",
          "L09",
          "L12"
        ],
        "existing_mechanism": "RootClientComponent统一挂Redux Provider，普通路径加载ssr:false的Main；FAQ直接渲染children。Main负责risk、通知、会话及页面生命周期等初始化。",
        "same": "都将通用能力放进外壳，把具体页面放在子内容中。",
        "different": "PwaSlotCash显式区分FAQ预渲染与客户端业务Main；Stake由node_ids/data启动分层layout。此处只比较启动组织，不评测SSR或首屏性能。",
        "not_comparable": "源码和打包证据不能直接证明线上首屏耗时或SSR效果。",
        "borrow": "保留RootClientComponent中的静态页分支；新增仅展示内容的页面时明确是否真的依赖Main初始化。"
      },
      "headline": "页面外壳已经分层，可沿现有静态页分支扩展。",
      "featured": true
    },
    {
      "id": "CMP-A02",
      "module": "路由预取与按需资源",
      "category": "架构",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/hooks/useIdleRoutePrefetch.ts",
          "line": 22,
          "end_line": 43,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "b752b741e0564044845bc3f0f45531007a747a418d21c0679e333fc39bd0829a"
        },
        {
          "path": "PwaSlotCash/src/app/hooks/useIdleRoutePrefetch.ts",
          "line": 85,
          "end_line": 105,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "b752b741e0564044845bc3f0f45531007a747a418d21c0679e333fc39bd0829a"
        }
      ],
      "stake_ids": [
        "SUP03",
        "SUP32"
      ],
      "current": "useIdleRoutePrefetch在非开发环境、非游戏页启用，空闲时依次预取有限路由，并保存已预取集合；清理时取消timeout和idle callback。",
      "difference": "Stake helper根据当前组件依赖表预载JS/CSS；项目这个hook按业务页面列表空闲预取。时机和粒度不同。",
      "suggestion": "维护少量高概率下一跳路由，并用真实点击率与网络请求验证收益；组件级资源继续使用已有dynamic/deferred工厂。",
      "limits": [
        "未测量包大小、网速或缓存命中，不据此声称哪边更快。"
      ],
      "raw": {
        "id": "CMP-A02",
        "module": "路由预取与按需资源",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/app/hooks/useIdleRoutePrefetch.ts",
            "line": 22,
            "end_line": 43,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "b752b741e0564044845bc3f0f45531007a747a418d21c0679e333fc39bd0829a"
          },
          {
            "path": "PwaSlotCash/src/app/hooks/useIdleRoutePrefetch.ts",
            "line": 85,
            "end_line": 105,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "b752b741e0564044845bc3f0f45531007a747a418d21c0679e333fc39bd0829a"
          }
        ],
        "stake_evidence_ids": [
          "SUP03",
          "SUP32"
        ],
        "existing_mechanism": "useIdleRoutePrefetch在非开发环境、非游戏页启用，空闲时依次预取有限路由，并保存已预取集合；清理时取消timeout和idle callback。",
        "same": "都有动态模块与延迟加载，项目已经具备预取能力。",
        "different": "Stake helper根据当前组件依赖表预载JS/CSS；项目这个hook按业务页面列表空闲预取。时机和粒度不同。",
        "not_comparable": "未测量包大小、网速或缓存命中，不据此声称哪边更快。",
        "borrow": "维护少量高概率下一跳路由，并用真实点击率与网络请求验证收益；组件级资源继续使用已有dynamic/deferred工厂。"
      },
      "headline": "预取已经存在，重点是下一跳选择和实际收益。",
      "featured": false
    },
    {
      "id": "CMP-I01",
      "module": "状态与持久化",
      "category": "交互",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/store.ts",
          "line": 12,
          "end_line": 23,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "c43d6bb4a033ac72a3b8a2aaf53c59a3e5bf37ab1f6769fa0b4de1e4e85001d9"
        },
        {
          "path": "PwaSlotCash/src/app/stores/gameCategoryStore.ts",
          "line": 401,
          "end_line": 414,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "0225f4c48edb6e857e90800883957587e52c559b4388cafa6491a71bc80e5623"
        }
      ],
      "stake_ids": [
        "UI01",
        "UI07",
        "UI08"
      ],
      "current": "Redux存userInfo/mainState等；Zustand gameCategoryStore按partialize存搜索历史、最近游玩、上次分类及分区/收藏配置；currentCategory每次默认为ALL。",
      "difference": "Stake侧栏与币种多用cookie store，项目游戏目录采用Zustand persist；持久化内容和业务目的不同。",
      "suggestion": "仅审查每个字段是否应该跨刷新/跨账户保存；保持已有Redux/Zustand职责，无需为了像Stake迁框架。",
      "limits": [
        "不能由框架名称判断渲染性能或可维护性。"
      ],
      "raw": {
        "id": "CMP-I01",
        "module": "状态与持久化",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/app/store.ts",
            "line": 12,
            "end_line": 23,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "c43d6bb4a033ac72a3b8a2aaf53c59a3e5bf37ab1f6769fa0b4de1e4e85001d9"
          },
          {
            "path": "PwaSlotCash/src/app/stores/gameCategoryStore.ts",
            "line": 401,
            "end_line": 414,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "0225f4c48edb6e857e90800883957587e52c559b4388cafa6491a71bc80e5623"
          }
        ],
        "stake_evidence_ids": [
          "UI01",
          "UI07",
          "UI08"
        ],
        "existing_mechanism": "Redux存userInfo/mainState等；Zustand gameCategoryStore按partialize存搜索历史、最近游玩、上次分类及分区/收藏配置；currentCategory每次默认为ALL。",
        "same": "两者都有reactive状态与选择性持久化；并非项目缺少状态管理。",
        "different": "Stake侧栏与币种多用cookie store，项目游戏目录采用Zustand persist；持久化内容和业务目的不同。",
        "not_comparable": "不能由框架名称判断渲染性能或可维护性。",
        "borrow": "仅审查每个字段是否应该跨刷新/跨账户保存；保持已有Redux/Zustand职责，无需为了像Stake迁框架。"
      },
      "headline": "已有状态与持久化，明确字段归属和账户边界。",
      "featured": false
    },
    {
      "id": "CMP-I02",
      "module": "路由与入口上下文",
      "category": "交互",
      "sources": [
        {
          "path": "PwaSlotCash/src/Core/RouterMgr.ts",
          "line": 45,
          "end_line": 68,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "fd6f48841a332c7a5f6711278c774236985a978f2d87767ff22afbe342dbe895"
        }
      ],
      "stake_ids": [
        "L14",
        "L15"
      ],
      "current": "RouterMgr封装Next router，store入口enterType存viewmodel，渠道params拼URL；login是/login独立路径。",
      "difference": "Stake modal由query驱动并从query生成modalProps；项目这里用页面路由+内存入口状态，不能刷新自动恢复所有内存来源。",
      "suggestion": "只给需要分享、刷新恢复或浏览器返回的登录/活动入口增加URL映射；自动奖励弹窗仍保留队列，避免把一切弹窗变成URL。",
      "limits": [
        "本次未全目录证明不存在任何URL弹窗；只比较RouterMgr与SDialog打开链。"
      ],
      "raw": {
        "id": "CMP-I02",
        "module": "路由与入口上下文",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/Core/RouterMgr.ts",
            "line": 45,
            "end_line": 68,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "fd6f48841a332c7a5f6711278c774236985a978f2d87767ff22afbe342dbe895"
          }
        ],
        "stake_evidence_ids": [
          "L14",
          "L15"
        ],
        "existing_mechanism": "RouterMgr封装Next router，store入口enterType存viewmodel，渠道params拼URL；login是/login独立路径。",
        "same": "都有共用导航入口，并保留来源参数。",
        "different": "Stake modal由query驱动并从query生成modalProps；项目这里用页面路由+内存入口状态，不能刷新自动恢复所有内存来源。",
        "not_comparable": "本次未全目录证明不存在任何URL弹窗；只比较RouterMgr与SDialog打开链。",
        "borrow": "只给需要分享、刷新恢复或浏览器返回的登录/活动入口增加URL映射；自动奖励弹窗仍保留队列，避免把一切弹窗变成URL。"
      },
      "headline": "页面路由与弹窗URL各有用途，按入口选择。",
      "featured": false
    },
    {
      "id": "CMP-I03",
      "module": "弹窗注册与生命周期",
      "category": "交互",
      "sources": [
        {
          "path": "PwaSlotCash/src/Master/SDialogFC/SDialogManagerFC.tsx",
          "line": 87,
          "end_line": 116,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "174244ea1bcfaf7dbd16e3b9923535c9d32de639cbd869bb846ccee81995679a"
        },
        {
          "path": "PwaSlotCash/src/Master/SDialogFC/SDialogManagerFC.tsx",
          "line": 127,
          "end_line": 142,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "174244ea1bcfaf7dbd16e3b9923535c9d32de639cbd869bb846ccee81995679a"
        }
      ],
      "stake_ids": [
        "L15"
      ],
      "current": "按fcKey/displayName注册dialogsMap，重复打开跳过；close删除记录通知Portal，只有notifyOnExternalClose=true才发onClose。",
      "difference": "Stake注册表以modal名+配置定义；项目以React组件+props登记，可支持多个不同key并存。",
      "suggestion": "给需要返回结果的弹窗统一约定用户完成/用户关闭/外部移除/加载失败几种结果，并沿用现有notifyOnExternalClose开关。",
      "limits": [
        "未运行Esc/焦点/返回行为；不能把源码功能候选当作完整可访问性审核。"
      ],
      "raw": {
        "id": "CMP-I03",
        "module": "弹窗注册与生命周期",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/Master/SDialogFC/SDialogManagerFC.tsx",
            "line": 87,
            "end_line": 116,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "174244ea1bcfaf7dbd16e3b9923535c9d32de639cbd869bb846ccee81995679a"
          },
          {
            "path": "PwaSlotCash/src/Master/SDialogFC/SDialogManagerFC.tsx",
            "line": 127,
            "end_line": 142,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "174244ea1bcfaf7dbd16e3b9923535c9d32de639cbd869bb846ccee81995679a"
          }
        ],
        "stake_evidence_ids": [
          "L15"
        ],
        "existing_mechanism": "按fcKey/displayName注册dialogsMap，重复打开跳过；close删除记录通知Portal，只有notifyOnExternalClose=true才发onClose。",
        "same": "两者都有统一容器、组件身份和生命周期，不需要另造管理器。",
        "different": "Stake注册表以modal名+配置定义；项目以React组件+props登记，可支持多个不同key并存。",
        "not_comparable": "未运行Esc/焦点/返回行为；不能把源码功能候选当作完整可访问性审核。",
        "borrow": "给需要返回结果的弹窗统一约定用户完成/用户关闭/外部移除/加载失败几种结果，并沿用现有notifyOnExternalClose开关。"
      },
      "headline": "已有弹窗身份管理，可统一完成与关闭结果。",
      "featured": true
    },
    {
      "id": "CMP-I04",
      "module": "弹窗资源按需加载",
      "category": "交互",
      "sources": [
        {
          "path": "PwaSlotCash/src/Master/SDialogFC/createDeferredDialog.tsx",
          "line": 72,
          "end_line": 91,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "313d9c31aaada9e808409cb8ed352c951561c609c770a6fd1002b56410c15a70"
        },
        {
          "path": "PwaSlotCash/src/Master/SDialogFC/createDeferredDialog.tsx",
          "line": 123,
          "end_line": 145,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "313d9c31aaada9e808409cb8ed352c951561c609c770a6fd1002b56410c15a70"
        },
        {
          "path": "PwaSlotCash/src/Master/SDialogFC/createDeferredDialog.tsx",
          "line": 164,
          "end_line": 172,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "313d9c31aaada9e808409cb8ed352c951561c609c770a6fd1002b56410c15a70"
        }
      ],
      "stake_ids": [
        "L14",
        "L15",
        "UI24"
      ],
      "current": "createDeferredDialog有薄壳身份、共享loadPromise、成功缓存、失败清promise重试；后台预加载等LCP，用户主动加载立即执行；加载失败确认当前shell后移除队列记录。",
      "difference": "项目显式区分LCP后台预加载和主动打开；Stake捕获源码能确认按需加载，未证实同样LCP调度。",
      "suggestion": "继续复用工厂，新增重弹窗不要绕过它；不要再以Stake为理由新建懒加载封装。",
      "limits": [
        "没有性能测量，不排名谁加载更快。"
      ],
      "raw": {
        "id": "CMP-I04",
        "module": "弹窗资源按需加载",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/Master/SDialogFC/createDeferredDialog.tsx",
            "line": 72,
            "end_line": 91,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "313d9c31aaada9e808409cb8ed352c951561c609c770a6fd1002b56410c15a70"
          },
          {
            "path": "PwaSlotCash/src/Master/SDialogFC/createDeferredDialog.tsx",
            "line": 123,
            "end_line": 145,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "313d9c31aaada9e808409cb8ed352c951561c609c770a6fd1002b56410c15a70"
          },
          {
            "path": "PwaSlotCash/src/Master/SDialogFC/createDeferredDialog.tsx",
            "line": 164,
            "end_line": 172,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "313d9c31aaada9e808409cb8ed352c951561c609c770a6fd1002b56410c15a70"
          }
        ],
        "stake_evidence_ids": [
          "L14",
          "L15",
          "UI24"
        ],
        "existing_mechanism": "createDeferredDialog有薄壳身份、共享loadPromise、成功缓存、失败清promise重试；后台预加载等LCP，用户主动加载立即执行；加载失败确认当前shell后移除队列记录。",
        "same": "同样采用动态import和旧实例取消守卫；项目已覆盖这一类加载失败恢复。",
        "different": "项目显式区分LCP后台预加载和主动打开；Stake捕获源码能确认按需加载，未证实同样LCP调度。",
        "not_comparable": "没有性能测量，不排名谁加载更快。",
        "borrow": "继续复用工厂，新增重弹窗不要绕过它；不要再以Stake为理由新建懒加载封装。"
      },
      "headline": "懒加载工厂已成熟，新增弹窗继续复用。",
      "featured": false
    },
    {
      "id": "CMP-I05",
      "module": "自动弹窗队列",
      "category": "交互",
      "sources": [
        {
          "path": "PwaSlotCash/src/Master/SDialogFC/DialogQueueManager.ts",
          "line": 24,
          "end_line": 37,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "32a8d277662d2798fafb68f2c9435c715bce9dcb57e1688ede963491e35b4a23"
        },
        {
          "path": "PwaSlotCash/src/Master/SDialogFC/DialogQueueManager.ts",
          "line": 45,
          "end_line": 65,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "32a8d277662d2798fafb68f2c9435c715bce9dcb57e1688ede963491e35b4a23"
        }
      ],
      "stake_ids": [
        "L15"
      ],
      "current": "enqueue按key去重+priority升序；单runner等待无弹窗，再轮询waitCondition，延迟后执行。",
      "difference": "Stake观察是modal注册与persistent；未抓到可直接对应的priority自动队列。",
      "suggestion": "在现有queue补显式取消/失效token和等待超时策略前先定义产品行为；waitCondition长期false目前会挡住后续项。",
      "limits": [
        "不能声称Stake没有自动队列，也不能按注册表比较队列优劣。"
      ],
      "raw": {
        "id": "CMP-I05",
        "module": "自动弹窗队列",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/Master/SDialogFC/DialogQueueManager.ts",
            "line": 24,
            "end_line": 37,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "32a8d277662d2798fafb68f2c9435c715bce9dcb57e1688ede963491e35b4a23"
          },
          {
            "path": "PwaSlotCash/src/Master/SDialogFC/DialogQueueManager.ts",
            "line": 45,
            "end_line": 65,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "32a8d277662d2798fafb68f2c9435c715bce9dcb57e1688ede963491e35b4a23"
          }
        ],
        "stake_evidence_ids": [
          "L15"
        ],
        "existing_mechanism": "enqueue按key去重+priority升序；单runner等待无弹窗，再轮询waitCondition，延迟后执行。",
        "same": "都集中处理模态交互，避免业务页直接散落弹窗生命周期。",
        "different": "Stake观察是modal注册与persistent；未抓到可直接对应的priority自动队列。",
        "not_comparable": "不能声称Stake没有自动队列，也不能按注册表比较队列优劣。",
        "borrow": "在现有queue补显式取消/失效token和等待超时策略前先定义产品行为；waitCondition长期false目前会挡住后续项。"
      },
      "headline": "队列能排顺序，等待失败时的产品行为值得补齐。",
      "featured": false
    },
    {
      "id": "CMP-I06",
      "module": "登录表单校验与反馈",
      "category": "交互",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/(auth)/login/modalPage.tsx",
          "line": 214,
          "end_line": 237,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "9cc2898ec8147de7e106e37339c819fc7edb14a1611dff31b87238fb804d2b5c"
        },
        {
          "path": "PwaSlotCash/src/app/(auth)/login/modalPage.tsx",
          "line": 245,
          "end_line": 271,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "9cc2898ec8147de7e106e37339c819fc7edb14a1611dff31b87238fb804d2b5c"
        },
        {
          "path": "PwaSlotCash/src/app/(auth)/login/modalPage.tsx",
          "line": 275,
          "end_line": 283,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "9cc2898ec8147de7e106e37339c819fc7edb14a1611dff31b87238fb804d2b5c"
        }
      ],
      "stake_ids": [
        "UI10",
        "UI11",
        "UI12"
      ],
      "current": "登录字段各维护error state，blur校验；提交先Geo校验，再reset错误和完整表单校验，按手机/邮箱/验证码分请求参数，finally关闭loading。",
      "difference": "Stake通用field store聚合touched/validating/validateOnChange/schema；项目login在组件内按条件手工管理，validateForm与onFieldBlur重复接触规则。",
      "suggestion": "抽一个轻量字段错误/touched/提交聚合机制复用已有validateForm纯函数，先在登录和注册各接一条；保留现有条件和Geo顺序。",
      "limits": [
        "地理检查、服务端账号规则两边不同，不能照抄Stake schema。"
      ],
      "raw": {
        "id": "CMP-I06",
        "module": "登录表单校验与反馈",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/app/(auth)/login/modalPage.tsx",
            "line": 214,
            "end_line": 237,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "9cc2898ec8147de7e106e37339c819fc7edb14a1611dff31b87238fb804d2b5c"
          },
          {
            "path": "PwaSlotCash/src/app/(auth)/login/modalPage.tsx",
            "line": 245,
            "end_line": 271,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "9cc2898ec8147de7e106e37339c819fc7edb14a1611dff31b87238fb804d2b5c"
          },
          {
            "path": "PwaSlotCash/src/app/(auth)/login/modalPage.tsx",
            "line": 275,
            "end_line": 283,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "9cc2898ec8147de7e106e37339c819fc7edb14a1611dff31b87238fb804d2b5c"
          }
        ],
        "stake_evidence_ids": [
          "UI10",
          "UI11",
          "UI12"
        ],
        "existing_mechanism": "登录字段各维护error state，blur校验；提交先Geo校验，再reset错误和完整表单校验，按手机/邮箱/验证码分请求参数，finally关闭loading。",
        "same": "都做失焦和提交校验，也有统一loading清理。",
        "different": "Stake通用field store聚合touched/validating/validateOnChange/schema；项目login在组件内按条件手工管理，validateForm与onFieldBlur重复接触规则。",
        "not_comparable": "地理检查、服务端账号规则两边不同，不能照抄Stake schema。",
        "borrow": "抽一个轻量字段错误/touched/提交聚合机制复用已有validateForm纯函数，先在登录和注册各接一条；保留现有条件和Geo顺序。"
      },
      "headline": "业务校验已存在，可共享字段生命周期。",
      "featured": true
    },
    {
      "id": "CMP-I07",
      "module": "游戏搜索与排序",
      "category": "交互",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/(pages)/components/GameSearch/GameSearch.tsx",
          "line": 32,
          "end_line": 43,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "2061a20d5de2efd63ed5ffe98809bba52900c101477ace22121cd53431a53e2d"
        },
        {
          "path": "PwaSlotCash/src/app/(pages)/components/GameSearch/GameSearch.tsx",
          "line": 148,
          "end_line": 171,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "2061a20d5de2efd63ed5ffe98809bba52900c101477ace22121cd53431a53e2d"
        },
        {
          "path": "PwaSlotCash/src/app/stores/gameCategoryStore.ts",
          "line": 163,
          "end_line": 177,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "0225f4c48edb6e857e90800883957587e52c559b4388cafa6491a71bc80e5623"
        }
      ],
      "stake_ids": [
        "UI14",
        "UI16",
        "L16",
        "L17",
        "L10"
      ],
      "current": "搜索全量分区去重游戏，按rewardType过滤；useDeferredValue延后非紧急结果渲染；slotName contains，先VIP解锁，再用(keyword.length*31+gameId)%1000排；历史trim去重保留10条。",
      "difference": "项目搜索实现可读且完整；Stake顶栏Csd-SZM5缺失，不能对比查询算法、分页、历史。Stake通用autocomplete是防抖异步请求，并非等价于useDeferredValue本地过滤。",
      "suggestion": "确认产品是否确实想按搜索词变化排序；当前只用词长度，同长度词排序键完全一致。需要词相关稳定顺序时改完整词hash，否则删“不同关键词随机”的误导性说明。",
      "limits": [
        "不能宣称Stake搜索比本项目更强。"
      ],
      "raw": {
        "id": "CMP-I07",
        "module": "游戏搜索与排序",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/app/(pages)/components/GameSearch/GameSearch.tsx",
            "line": 32,
            "end_line": 43,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "2061a20d5de2efd63ed5ffe98809bba52900c101477ace22121cd53431a53e2d"
          },
          {
            "path": "PwaSlotCash/src/app/(pages)/components/GameSearch/GameSearch.tsx",
            "line": 148,
            "end_line": 171,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "2061a20d5de2efd63ed5ffe98809bba52900c101477ace22121cd53431a53e2d"
          },
          {
            "path": "PwaSlotCash/src/app/stores/gameCategoryStore.ts",
            "line": 163,
            "end_line": 177,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "0225f4c48edb6e857e90800883957587e52c559b4388cafa6491a71bc80e5623"
          }
        ],
        "stake_evidence_ids": [
          "UI14",
          "UI16",
          "L16",
          "L17",
          "L10"
        ],
        "existing_mechanism": "搜索全量分区去重游戏，按rewardType过滤；useDeferredValue延后非紧急结果渲染；slotName contains，先VIP解锁，再用(keyword.length*31+gameId)%1000排；历史trim去重保留10条。",
        "same": "都有独立搜索开关，受限制条目排序有共通思路（Stake推荐isBlocked后置、项目VIP未解锁后置）。",
        "different": "项目搜索实现可读且完整；Stake顶栏Csd-SZM5缺失，不能对比查询算法、分页、历史。Stake通用autocomplete是防抖异步请求，并非等价于useDeferredValue本地过滤。",
        "not_comparable": "不能宣称Stake搜索比本项目更强。",
        "borrow": "确认产品是否确实想按搜索词变化排序；当前只用词长度，同长度词排序键完全一致。需要词相关稳定顺序时改完整词hash，否则删“不同关键词随机”的误导性说明。"
      },
      "headline": "本地目录搜索已有实现，Stake全局搜索源码仍缺。",
      "featured": true
    },
    {
      "id": "CMP-I08",
      "module": "首页数据组合与过期响应",
      "category": "交互",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/(pages)/home/page.tsx",
          "line": 198,
          "end_line": 227,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "86da94e371d2cf3943aa23cb5f4eb71d553305aab09c1f8483ae9abe3763773c"
        },
        {
          "path": "PwaSlotCash/src/app/(pages)/home/page.tsx",
          "line": 314,
          "end_line": 318,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "86da94e371d2cf3943aa23cb5f4eb71d553305aab09c1f8483ae9abe3763773c"
        }
      ],
      "stake_ids": [
        "L02",
        "L09",
        "L03",
        "UI14"
      ],
      "current": "首页recommend+base config并发且逐请求catch；更新store后Live延后1500ms；effect按userId重跑，cleanup仅清liveTimer。",
      "difference": "Stake游戏active-bet检查有闭包递增序号只接受最新响应；项目这段首页effect没有写store前的用户generation检查或取消标记。",
      "suggestion": "给这段effect增加generation/cancelled判定，尤其在setPartitionsConfig、setFavoriteMachineList和设置Live定时器前；快速切账号时旧结果应不能发布。",
      "limits": [
        "这不是已复现串号bug；本次只读静态，网络层是否另有全局隔离未确认。"
      ],
      "raw": {
        "id": "CMP-I08",
        "module": "首页数据组合与过期响应",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/app/(pages)/home/page.tsx",
            "line": 198,
            "end_line": 227,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "86da94e371d2cf3943aa23cb5f4eb71d553305aab09c1f8483ae9abe3763773c"
          },
          {
            "path": "PwaSlotCash/src/app/(pages)/home/page.tsx",
            "line": 314,
            "end_line": 318,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "86da94e371d2cf3943aa23cb5f4eb71d553305aab09c1f8483ae9abe3763773c"
          }
        ],
        "stake_evidence_ids": [
          "L02",
          "L09",
          "L03",
          "UI14"
        ],
        "existing_mechanism": "首页recommend+base config并发且逐请求catch；更新store后Live延后1500ms；effect按userId重跑，cleanup仅清liveTimer。",
        "same": "已采用并发加载、局部失败隔离和非关键资源延后；这是可以保留的实现。",
        "different": "Stake游戏active-bet检查有闭包递增序号只接受最新响应；项目这段首页effect没有写store前的用户generation检查或取消标记。",
        "not_comparable": "这不是已复现串号bug；本次只读静态，网络层是否另有全局隔离未确认。",
        "borrow": "给这段effect增加generation/cancelled判定，尤其在setPartitionsConfig、setFavoriteMachineList和设置Live定时器前；快速切账号时旧结果应不能发布。"
      },
      "headline": "首页切用户后的旧响应回写值得优先核验。",
      "featured": true
    },
    {
      "id": "CMP-I09",
      "module": "节流与请求正确性边界",
      "category": "交互",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/hooks/useStableDataFetch.tsx",
          "line": 24,
          "end_line": 43,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "dff7bed0aac194518fec047705165f3d963512f797e301c978273050b42e58bd"
        }
      ],
      "stake_ids": [
        "UI14",
        "L03"
      ],
      "current": "useStableDataFetch限制默认1000ms最小调用间隔，再用500ms trailing debounce，更新lastCallTime后调用fetchFunction。",
      "difference": "这个hook限制启动频率，不等待/取消Promise，不保证结果新旧顺序；Stake autocomplete的最新任务守卫解决的是发布正确性，不能用节流替代。",
      "suggestion": "保留hook负责频率；只有会覆盖UI状态的异步任务单独加最新结果守卫，不把所有请求都加全局锁。",
      "limits": [
        "项目其他request层可能做了dedupe，本条只讨论hook职责。"
      ],
      "raw": {
        "id": "CMP-I09",
        "module": "节流与请求正确性边界",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/app/hooks/useStableDataFetch.tsx",
            "line": 24,
            "end_line": 43,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "dff7bed0aac194518fec047705165f3d963512f797e301c978273050b42e58bd"
          }
        ],
        "stake_evidence_ids": [
          "UI14",
          "L03"
        ],
        "existing_mechanism": "useStableDataFetch限制默认1000ms最小调用间隔，再用500ms trailing debounce，更新lastCallTime后调用fetchFunction。",
        "same": "双方都有减少重复工作机制。",
        "different": "这个hook限制启动频率，不等待/取消Promise，不保证结果新旧顺序；Stake autocomplete的最新任务守卫解决的是发布正确性，不能用节流替代。",
        "not_comparable": "项目其他request层可能做了dedupe，本条只讨论hook职责。",
        "borrow": "保留hook负责频率；只有会覆盖UI状态的异步任务单独加最新结果守卫，不把所有请求都加全局锁。"
      },
      "headline": "节流负责频率，结果守卫负责状态正确性。",
      "featured": false
    },
    {
      "id": "CMP-I10",
      "module": "游戏入口门控与加载壳",
      "category": "交互",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/(game)/game/page.tsx",
          "line": 71,
          "end_line": 83,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "b4dcbe09129e1d0d6b08402e4ed13863bc434301e241d1765a8b762574867049"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/game/page.tsx",
          "line": 98,
          "end_line": 100,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "b4dcbe09129e1d0d6b08402e4ed13863bc434301e241d1765a8b762574867049"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/layout.tsx",
          "line": 1,
          "end_line": 7,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "398bd3a4909bce355a3eb7c5fed108ae9924e64b04a019079c22664560b8c497"
        }
      ],
      "stake_ids": [
        "L04",
        "L05",
        "L07"
      ],
      "current": "从query game选配置，缺失/状态0/VIP不足阻断；frame load消息结束外层loading。route layout自身仅CSS包装。",
      "difference": "Stake共享game layout还组装provider关注/SEO/奖金限制与停用游戏完成回合例外；本项目Unity frame壳关注VIP准入与frame加载阶段。",
      "suggestion": "将当前game准入判定保持可单测纯函数；新增不同游戏承载形式时再抽公共壳，现阶段不需要为Stake架构重构所有页。",
      "limits": [
        "第三方与Unity协议、奖金体系不同；不应机械照抄停用游戏例外或关注栏。"
      ],
      "raw": {
        "id": "CMP-I10",
        "module": "游戏入口门控与加载壳",
        "project_sources": [
          {
            "path": "PwaSlotCash/src/app/(game)/game/page.tsx",
            "line": 71,
            "end_line": 83,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "b4dcbe09129e1d0d6b08402e4ed13863bc434301e241d1765a8b762574867049"
          },
          {
            "path": "PwaSlotCash/src/app/(game)/game/page.tsx",
            "line": 98,
            "end_line": 100,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "b4dcbe09129e1d0d6b08402e4ed13863bc434301e241d1765a8b762574867049"
          },
          {
            "path": "PwaSlotCash/src/app/(game)/layout.tsx",
            "line": 1,
            "end_line": 7,
            "snippet": "[项目代码保留在本地；线上版仅提供定位]",
            "sha256": "398bd3a4909bce355a3eb7c5fed108ae9924e64b04a019079c22664560b8c497"
          }
        ],
        "stake_evidence_ids": [
          "L04",
          "L05",
          "L07"
        ],
        "existing_mechanism": "从query game选配置，缺失/状态0/VIP不足阻断；frame load消息结束外层loading。route layout自身仅CSS包装。",
        "same": "双方在游戏入口先检查目录/状态再显示游戏壳，而不是点卡片直接启动。",
        "different": "Stake共享game layout还组装provider关注/SEO/奖金限制与停用游戏完成回合例外；本项目Unity frame壳关注VIP准入与frame加载阶段。",
        "not_comparable": "第三方与Unity协议、奖金体系不同；不应机械照抄停用游戏例外或关注栏。",
        "borrow": "将当前game准入判定保持可单测纯函数；新增不同游戏承载形式时再抽公共壳，现阶段不需要为Stake架构重构所有页。"
      },
      "headline": "入口已有准入条件，承载形式不同需分开比较。",
      "featured": false
    },
    {
      "id": "GUI01",
      "module": "游戏发现/分类/懒挂载",
      "category": "游戏与适配",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/(pages)/components/GameCategoryContent/GameCategoryContent.tsx",
          "line": 60,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "bb949f356c73de6073e3130a71aa2b5fa64dbb9326a7a9e826b6fcd7c9770617"
        },
        {
          "path": "PwaSlotCash/src/app/_components/GameGrid/GameGrid.tsx",
          "line": 19,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "a1cc74089d77b5609ca1930a78d4603cb5e2411b406e45511958b70b49f15c15"
        },
        {
          "path": "PwaSlotCash/src/app/_components/GameGrid/GameGrid.tsx",
          "line": 95,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "a1cc74089d77b5609ca1930a78d4603cb5e2411b406e45511958b70b49f15c15"
        }
      ],
      "stake_ids": [
        "SUP31",
        "SUP32"
      ],
      "current": "已有分区视口10%+50px提前挂载，首区立即显示；45个游戏起启用按行虚拟列表，overscan4，虚拟行不反复入场动画。",
      "difference": "Stake首页将recent/trending/recommendation/originals分离数据视图，推荐携带modelVersion/sequenceId；Pwa本组件明示优先采用后端index而非前端重算，且已有收藏独立排序。",
      "suggestion": "保留现有虚拟化与后端排序；只按业务需要补推荐版本/展示批次归因，统一section loading/empty/error三态，避免为对齐Stake引入第二套排序系统。",
      "limits": [
        "未核验当前后端推荐数据格式或真实滚动性能；源码注释与执行分支分开。"
      ],
      "raw": {
        "id": "GUI01",
        "module": "游戏发现/分类/懒挂载",
        "pwa_evidence": [
          {
            "source": "PwaSlotCash/src/app/(pages)/components/GameCategoryContent/GameCategoryContent.tsx",
            "line_1_based": 60,
            "snippet": "}> = ({ section, games, sectionIndex, onViewAllClick, isNew, isLive, skipAnimation }) => {\n    const shouldReduceMotion = useReducedMotion();\n    const ref = useRef<HTMLDivElement>(null);\n    const [inViewport] = useInViewport(ref, {\n        threshold: 0.1, // 当 10% 的元素进入视口时触发\n        rootMargin: '50px', // 提前 50px 开始加载\n    });\n    const [hasLoaded, setHasLoaded] = useState(sectionIndex === 0);\n\n    // 一旦进入视口，就标记为已加载（不再卸载）\n    useEffect(() => {\n        if (inViewport && !hasLoaded) {\n            setHasLoaded(true);\n        }\n    }, [inViewport, hasLoaded]);"
          },
          {
            "source": "PwaSlotCash/src/app/_components/GameGrid/GameGrid.tsx",
            "line_1_based": 19,
            "snippet": "const COLUMN_COUNT = 3;\n// 约三屏内容后启用虚拟化；手机实际只挂载可见区域及上下缓冲行。\nconst VIRTUALIZE_THRESHOLD = 45;\nconst OVERSCAN_ROWS = 4;\n// 固定首屏参数保证 SSG 与客户端首次渲染一致，挂载后由 measureElement 校准。\nconst INITIAL_RECT = { width: 1080, height: 1200 };\nconst ESTIMATED_ROW_HEIGHT = 240;"
          },
          {
            "source": "PwaSlotCash/src/app/_components/GameGrid/GameGrid.tsx",
            "line_1_based": 95,
            "snippet": "    const virtualizer = useWindowVirtualizer({\n        count: rows.length,\n        estimateSize: () => ESTIMATED_ROW_HEIGHT,\n        initialOffset: 0,\n        initialRect: INITIAL_RECT,\n        overscan: OVERSCAN_ROWS,\n        scrollMargin,\n    });"
          }
        ],
        "existing_strengths_zh": "已有分区视口10%+50px提前挂载，首区立即显示；45个游戏起启用按行虚拟列表，overscan4，虚拟行不反复入场动画。",
        "confirmed_difference_zh": "Stake首页将recent/trending/recommendation/originals分离数据视图，推荐携带modelVersion/sequenceId；Pwa本组件明示优先采用后端index而非前端重算，且已有收藏独立排序。",
        "suggestion_zh": "保留现有虚拟化与后端排序；只按业务需要补推荐版本/展示批次归因，统一section loading/empty/error三态，避免为对齐Stake引入第二套排序系统。",
        "stake_reference_ids": [
          "SUP31",
          "SUP32"
        ],
        "limitations": [
          "未核验当前后端推荐数据格式或真实滚动性能；源码注释与执行分支分开。"
        ]
      },
      "headline": "分区懒挂载与虚拟列表已经做了。",
      "featured": true
    },
    {
      "id": "GUI02",
      "module": "游戏卡片视觉/交互",
      "category": "游戏与适配",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/(pages)/components/GameItem/GameItem.module.css",
          "line": 1,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "8062a860d2e93e8c590862a103a31061c5d89282bb833ef3ffb1a3f631a14d24"
        },
        {
          "path": "PwaSlotCash/src/app/(pages)/components/GameItem/GameItem.tsx",
          "line": 358,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "46b1c3a982743b7c462e920f66a269439e9e6d71a74812e272dad918e8c32b29"
        },
        {
          "path": "PwaSlotCash/src/app/(pages)/components/GameItem/GameItem.tsx",
          "line": 281,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "46b1c3a982743b7c462e920f66a269439e9e6d71a74812e272dad918e8c32b29"
        }
      ],
      "stake_ids": [
        "SUP16"
      ],
      "current": "卡片cover占位有明确aspect-ratio，hover能力查询已避免触屏粘滞；收藏stopPropagation，拖拽后禁止误入游戏。",
      "difference": "Pwa卡比例2.52/3.41≈.739、desktop hover scale1.05、active .95；Stake约.7455、hover向上4%，触屏clicked显示操作按钮。Pwa入口这里是div onClick，片段未见键盘/role，而Stake卡是链接样式。",
      "suggestion": "保留比例和移动反馈；把导航入口做语义链接或按钮，收藏仍独立按钮；仅需要二级操作时借鉴hover/touch按钮模式，不强制改成双击启动。",
      "limits": [
        "不能从StakeCSS独自确认DOM语义或完整键盘行为；Pwa可访问性结论限定该render片段。"
      ],
      "raw": {
        "id": "GUI02",
        "module": "游戏卡片视觉/交互",
        "pwa_evidence": [
          {
            "source": "PwaSlotCash/src/app/(pages)/components/GameItem/GameItem.module.css",
            "line_1_based": 1,
            "snippet": ".item {\n    cursor: pointer;\n    width: 100%;\n    aspect-ratio: 2.52 / 3.41;\n    position: relative;\n    border-radius: 4px;\n    transition: transform 0.1s !important;\n    /* overflow: hidden; */\n}\n\n.game_image {\n    border-radius: 6px;\n}\n\n/* 覆盖全局 btn:hover，防止移动端粘滞 */\n.item:hover {\n    transform: none !important;\n}\n\n/* 仅在支持 hover 的设备上启用放大 */\n@media (hover: hover) {\n    .item:hover {\n        transform: scale(1.05) !important;\n    }\n}\n\n/* 恢复点击反馈 */\n.item:active {\n    transform: scale(0.95) !important;"
          },
          {
            "source": "PwaSlotCash/src/app/(pages)/components/GameItem/GameItem.tsx",
            "line_1_based": 358,
            "snippet": "    return (\n        <>\n            <div\n                id={`card-game-${gameId}`}\n                ref={gameItemRef}\n                className={`${styles.item} btn`}\n                onMouseDown={() => (isDraggingRef.current = false)}\n                onMouseMove={() => (isDraggingRef.current = true)}\n                onClick={handleClick}\n            >\n                {isLive && <div className={styles.live_badge}>LIVE</div>}\n                <OssImage\n                    className={styles.game_image}\n                    src={gameIconSrc}\n                    alt={`game_${gameId}`}\n                    fill\n                    style={{ objectFit: 'cover' }}\n                    loading={imageLoading}\n                />"
          },
          {
            "source": "PwaSlotCash/src/app/(pages)/components/GameItem/GameItem.tsx",
            "line_1_based": 281,
            "snippet": "    const handleFavoriteClick = (e: React.MouseEvent) => {\n        e.stopPropagation();\n        runToggleFavorite();\n    };\n\n    const handleClick = () => {\n        if (!isDraggingRef.current) {\n            try {\n                onEnterGame();\n            } catch {}\n        }\n        isDraggingRef.current = false;"
          }
        ],
        "existing_strengths_zh": "卡片cover占位有明确aspect-ratio，hover能力查询已避免触屏粘滞；收藏stopPropagation，拖拽后禁止误入游戏。",
        "confirmed_difference_zh": "Pwa卡比例2.52/3.41≈.739、desktop hover scale1.05、active .95；Stake约.7455、hover向上4%，触屏clicked显示操作按钮。Pwa入口这里是div onClick，片段未见键盘/role，而Stake卡是链接样式。",
        "suggestion_zh": "保留比例和移动反馈；把导航入口做语义链接或按钮，收藏仍独立按钮；仅需要二级操作时借鉴hover/touch按钮模式，不强制改成双击启动。",
        "stake_reference_ids": [
          "SUP16"
        ],
        "limitations": [
          "不能从StakeCSS独自确认DOM语义或完整键盘行为；Pwa可访问性结论限定该render片段。"
        ]
      },
      "headline": "已有卡片视觉，键盘与焦点可作为下一层完善。",
      "featured": false
    },
    {
      "id": "GUI03",
      "module": "封面资源与首屏优先级",
      "category": "游戏与适配",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/_components/OssImage/index.tsx",
          "line": 24,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "8e4b7e0fdb0aef46efc39c53a6c176273cb4ef3dbf23dc84f0117a3ef5974027"
        },
        {
          "path": "PwaSlotCash/src/app/_components/OssImage/index.tsx",
          "line": 39,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "8e4b7e0fdb0aef46efc39c53a6c176273cb4ef3dbf23dc84f0117a3ef5974027"
        },
        {
          "path": "PwaSlotCash/src/app/(pages)/components/Banner/Banner.tsx",
          "line": 159,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "594ce8780e1da342a9da57325f3d55de466546c802ad65cb67259a14541a88a7"
        },
        {
          "path": "PwaSlotCash/src/app/config/preloadImages.ts",
          "line": 5,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "427fd5992084050daff1578721cf9769aa0f0cfb3955bfe0a1d70088df978af6"
        }
      ],
      "stake_ids": [
        "SUP16",
        "SUP32"
      ],
      "current": "已用ThumbHash背景避免白闪，成功/失败清占位；首banner eager/high，其他lazy/low；关键图片与idle装饰分开。",
      "difference": "Stake有placeholder/image-focus等模块与lazy homepage boards；Pwa已经有更明确的首页关键图片清单和网络优先级，不能列成缺失。",
      "suggestion": "核对GameItem的OssImage fill是否应传与三列/横排匹配sizes；按数据证明重复preload是否必要。沿用关键4图/idle列表，勿全量预载游戏库。",
      "limits": [
        "未测真实LCP与资源瀑布；ThumbHash是否全目录覆盖需生成清单验证。"
      ],
      "raw": {
        "id": "GUI03",
        "module": "封面资源与首屏优先级",
        "pwa_evidence": [
          {
            "source": "PwaSlotCash/src/app/_components/OssImage/index.tsx",
            "line_1_based": 24,
            "snippet": "    const placeholder =\n        showPlaceholder && src && !src.startsWith('data:')\n            ? (placeholderSrc ?? getImageThumbHash(src))\n            : undefined;\n    const showBackground = loadedSrc !== imageSrc && placeholder;\n\n    useLayoutEffect(() => {"
          },
          {
            "source": "PwaSlotCash/src/app/_components/OssImage/index.tsx",
            "line_1_based": 39,
            "snippet": "            ref={imageRef}\n            src={imageSrc}\n            style={{\n                backgroundImage: showBackground ? `url(\"${placeholder}\")` : undefined,\n                backgroundSize: 'cover',\n                backgroundPosition: 'center',\n                // 原图就绪后短暂淡入，柔化占位图切换。\n                opacity: showBackground ? 0.85 : 1,\n                transition: 'opacity 80ms ease-out',\n                ...style,\n            }}\n            onLoad={(e) => {\n                if (e.currentTarget.naturalWidth > 0) {\n                    setLoadedSrc(imageSrc);\n                }\n                onLoad?.(e);\n            }}\n            onError={(event) => {\n                setLoadedSrc(undefined);\n                onError?.(event);\n            }}"
          },
          {
            "source": "PwaSlotCash/src/app/(pages)/components/Banner/Banner.tsx",
            "line_1_based": 159,
            "snippet": "                alt=''\n                fill\n                sizes='(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw'\n                // 仅首张 eager+high，其余 lazy+low，避免非首帧抢占 LCP。\n                loading={highPriority ? 'eager' : 'lazy'}\n                fetchPriority={highPriority ? 'high' : 'low'}\n                style={{ objectFit: 'cover' }}\n                onLoad={() => notifyFirstBannerReady(true)}\n                onError={() => notifyFirstBannerReady(false)}"
          },
          {
            "source": "PwaSlotCash/src/app/config/preloadImages.ts",
            "line_1_based": 5,
            "snippet": "export const PRELOAD_IMAGES = [\n    // 真 Banner 首图，配合 Banner 首张 eager+high\n    '/img/banner/banner4.webp',\n    '/img/home_bg.webp',\n    '/img/gold.png',\n    '/img/ico_gc_big.png'\n];\n\n/** 空闲时再预载的次要首页图（不阻塞 LCP） */\nexport const IDLE_PRELOAD_IMAGES = [\n    '/img/home/banner.webp',\n    '/img/home/banner2.webp',\n    '/img/store/small_coins.webp',\n    // 奖励弹窗\n    '/img/general-gift/star_sm_left.webp',\n    '/img/general-gift/star_right.webp',\n    '/img/general-gift/line_right.webp',\n    '/img/general-gift/line_sm_right.webp',\n    '/img/general-gift/line_left.webp',\n    '/img/general-gift/line_red_left.webp'"
          }
        ],
        "existing_strengths_zh": "已用ThumbHash背景避免白闪，成功/失败清占位；首banner eager/high，其他lazy/low；关键图片与idle装饰分开。",
        "confirmed_difference_zh": "Stake有placeholder/image-focus等模块与lazy homepage boards；Pwa已经有更明确的首页关键图片清单和网络优先级，不能列成缺失。",
        "suggestion_zh": "核对GameItem的OssImage fill是否应传与三列/横排匹配sizes；按数据证明重复preload是否必要。沿用关键4图/idle列表，勿全量预载游戏库。",
        "stake_reference_ids": [
          "SUP16",
          "SUP32"
        ],
        "limitations": [
          "未测真实LCP与资源瀑布；ThumbHash是否全目录覆盖需生成清单验证。"
        ]
      },
      "headline": "图片占位与优先级已有机制，先保留并验证。",
      "featured": false
    },
    {
      "id": "GUI04",
      "module": "供应商iframe加载与协议生命周期",
      "category": "游戏与适配",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/(game)/components/GameFrame/GameFrame.tsx",
          "line": 83,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "7964d7ef54d9982a76cc898d02f178c6455ecb18107a2004f3e385125e44601e"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/components/GameFrame/GameFrame.tsx",
          "line": 181,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "7964d7ef54d9982a76cc898d02f178c6455ecb18107a2004f3e385125e44601e"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/components/GameFrame/GameFrame.tsx",
          "line": 245,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "7964d7ef54d9982a76cc898d02f178c6455ecb18107a2004f3e385125e44601e"
        },
        {
          "path": "PwaSlotCash/src/app/(game2)/game2/page.tsx",
          "line": 196,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "fb103b79546812cd3f5cae24f1dc1c0d46a8db9353443be039dc3788bc2b57ad"
        }
      ],
      "stake_ids": [
        "SUP22",
        "SUP24",
        "D09",
        "D18"
      ],
      "current": "已有方向适配卸载清理、完整初始化失败后minimal协议降级、gameID迟到回调过滤、防重复load埋点、加载阶段计时。",
      "difference": "Stake内建Dice是同应用client state machine，Pwa主要嵌iframe/Unity；不能因无Dice下注状态机说Pwa游戏容器缺业务逻辑。合理对比是共享壳加载/错误/生命周期，不是把自研下注逻辑复制到供应商容器。",
      "suggestion": "围绕idle/loading/ready/error/leaving和一次启动sessionID统一壳状态，复用现有计时与late回调防护；iframe业务数据与供应商结算仍由现有协议负责。",
      "limits": [
        "未执行iframe内游戏或供应商结算；只确认父窗口生命周期。"
      ],
      "raw": {
        "id": "GUI04",
        "module": "供应商iframe加载与协议生命周期",
        "pwa_evidence": [
          {
            "source": "PwaSlotCash/src/app/(game)/components/GameFrame/GameFrame.tsx",
            "line_1_based": 83,
            "snippet": "    useEffect(() => {\n        if (iframeRef?.current) {\n            const gameItem = getGameItemDataId(gameId);\n            const defaultDirection = (gameItem && gameItem.defaultDirection) || 1;\n            FrameOrientMgr.init(iframeRef.current, defaultDirection);\n        }\n        return () => {\n            FrameOrientMgr.Remove();\n        };\n    }, [gameId]);\n\n    // 初始化立即设置 iframe 源信息\n    useEffect(() => {\n        GameMsgUtil.iframeOrigin = confJson.iframeOrigin!;\n    }, []);"
          },
          {
            "source": "PwaSlotCash/src/app/(game)/components/GameFrame/GameFrame.tsx",
            "line_1_based": 181,
            "snippet": "        try {\n            GameMsgUtil.sendMsgLower(initData);\n        } catch (error) {\n            // 记录降级原因和游戏 ID，用于区分完整协议失败与游戏自身加载失败。\n            Sentry.captureException(error, {\n                tags: {\n                    location: '[GameFrame]game_init_data',\n                    gameId: String(gameId),\n                    fallback: 'minimal_init_data',\n                },\n            });\n            // 最小协议已经由 BridgeFrame 使用：仅保证 iframe 能继续初始化，\n            // 不再携带全量 localStorage、GameDB、/idbfs 和游戏配置，避免重复 clone 失败。\n            GameMsgUtil.sendMsgLower({\n                type: GameMsgKey.game_init_data,\n                data: {},\n                gameId,\n                header: KitAPI.GetHeaderStr(),\n            });\n        }"
          },
          {
            "source": "PwaSlotCash/src/app/(game)/components/GameFrame/GameFrame.tsx",
            "line_1_based": 245,
            "snippet": "    return (\n        <iframe\n            className={styles.content}\n            ref={iframeRef}\n            src={gameUrl}\n            title=''\n            sandbox='allow-scripts allow-same-origin allow-popups'\n            onLoad={onIframeOk}\n        ></iframe>"
          },
          {
            "source": "PwaSlotCash/src/app/(game2)/game2/page.tsx",
            "line_1_based": 196,
            "snippet": "    // 处理游戏加载完成\n    const handleGameLoaded = useCallback(\n        (loadedGameId: number) => {\n            // 忽略切换机台后旧 iframe 的迟到回调，避免污染当前机台耗时与防重状态。\n            if (loadedGameId && loadedGameId !== gameId) return;\n\n            reportSlotGameReady(true);\n\n            if (!hasTrackedSlotGameShow.current) {\n                hasTrackedSlotGameShow.current = true;\n\n                const loadingTiming = loadingTimingRef.current;\n                const aMetric = getLoadingMetric(\n                    loadingTiming?.gameId === gameId ? loadingTiming.aStartedAt : null,\n                    getLoadingTimestamp(),\n                );\n\n                TrackGameLoad.showThirdPartyGameSlot(userId, vipLevel, gameId, {\n                    glLoadingATime: aMetric.time,\n                    glLoadingATimeInterval: aMetric.interval,"
          }
        ],
        "existing_strengths_zh": "已有方向适配卸载清理、完整初始化失败后minimal协议降级、gameID迟到回调过滤、防重复load埋点、加载阶段计时。",
        "confirmed_difference_zh": "Stake内建Dice是同应用client state machine，Pwa主要嵌iframe/Unity；不能因无Dice下注状态机说Pwa游戏容器缺业务逻辑。合理对比是共享壳加载/错误/生命周期，不是把自研下注逻辑复制到供应商容器。",
        "suggestion_zh": "围绕idle/loading/ready/error/leaving和一次启动sessionID统一壳状态，复用现有计时与late回调防护；iframe业务数据与供应商结算仍由现有协议负责。",
        "stake_reference_ids": [
          "SUP22",
          "SUP24",
          "D09",
          "D18"
        ],
        "limitations": [
          "未执行iframe内游戏或供应商结算；只确认父窗口生命周期。"
        ]
      },
      "headline": "iframe已有加载防护，可进一步统一阶段状态。",
      "featured": true
    },
    {
      "id": "GUI05",
      "module": "消息接收与退出清理边界",
      "category": "游戏与适配",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/hooks/useGameMessage.tsx",
          "line": 19,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "05aaeba2ee98bd71100cf969f65b4fcafe139607f84e2814193ae7335cebc207"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/game/UnityResourceMgr.ts",
          "line": 8,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "d719bcef8d9d6807ad2672906a972482287b2a6b3d899bd77cb35cf9186ae77d"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/game/UnityResourceMgr.ts",
          "line": 36,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "d719bcef8d9d6807ad2672906a972482287b2a6b3d899bd77cb35cf9186ae77d"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/game/UnityResourceMgr.ts",
          "line": 48,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "d719bcef8d9d6807ad2672906a972482287b2a6b3d899bd77cb35cf9186ae77d"
        }
      ],
      "stake_ids": [
        "SUP24"
      ],
      "current": "监听按allowedOrigin过滤、handlerRef避免旧闭包，effect卸载移除listener；gameWindow更新刷新队列；提供stopMessageCleanup。",
      "difference": "接收hook只验证origin和game_前缀，未在此hook验证event.source=当前iframe；quitAndGoByFrom只导航，不在这个函数调用stopMessageCleanup或清gameWindow。Stake共享wrapper退出有unsubscribe/reset的一致路径。",
      "suggestion": "将当前iframe.contentWindow/sessionID校验与统一离开清理集中在壳层；确认其他入口是否已负责timer/window清理后再改，不把未发现调用写成全仓缺失。",
      "limits": [
        "限定本hook和quit方法，不声称存在可利用漏洞或全仓无清理。"
      ],
      "raw": {
        "id": "GUI05",
        "module": "消息接收与退出清理边界",
        "pwa_evidence": [
          {
            "source": "PwaSlotCash/src/app/hooks/useGameMessage.tsx",
            "line_1_based": 19,
            "snippet": "    useEffect(() => {\n        const handleMessage = (event: MessageEvent) => {\n            if (Util.isLog) {\n                LogUtil.info(GameMsgUtil.tag1, 'useGameMessage event.origin', event.origin);\n                LogUtil.info(GameMsgUtil.tag1, 'useGameMessage allowedOrigin', allowedOrigin);\n            }\n            if (event.origin !== allowedOrigin) return;\n            const data: any = event.data;\n\n            if (Util.isLog) {\n                LogUtil.info(GameMsgUtil.tag1, 'useGameMessage', data);\n            }\n            if (data.type && data.type.startsWith('game_')) {\n                handlerRef.current(data);\n            }\n        };\n        window.addEventListener('message', handleMessage);\n        return () => {\n            window.removeEventListener('message', handleMessage);\n        };"
          },
          {
            "source": "PwaSlotCash/src/app/(game)/game/UnityResourceMgr.ts",
            "line_1_based": 8,
            "snippet": "    static UpdateGameWindow(contentWindow: Window) {\n        this.gameWindow = contentWindow;\n\n        // 当gameWindow更新时，处理队列中的消息\n        GameMsgUtil.flushMessageQueue();\n\n        // 启动定期清理过期消息的定时器\n        this.startMessageCleanup();"
          },
          {
            "source": "PwaSlotCash/src/app/(game)/game/UnityResourceMgr.ts",
            "line_1_based": 36,
            "snippet": "    static stopMessageCleanup() {\n        if (this.cleanupTimer) {\n            clearInterval(this.cleanupTimer);\n            this.cleanupTimer = null;\n        }"
          },
          {
            "source": "PwaSlotCash/src/app/(game)/game/UnityResourceMgr.ts",
            "line_1_based": 48,
            "snippet": "    static quitAndGoByFrom(from?: string) {\n        switch (from) {\n            // 金币cash back活动页\n            case 'cashback':\n                RouterMgr.goActivity('cashback');\n                break;\n            default:\n                RouterMgr.home('&from=game');\n        }\n    }"
          }
        ],
        "existing_strengths_zh": "监听按allowedOrigin过滤、handlerRef避免旧闭包，effect卸载移除listener；gameWindow更新刷新队列；提供stopMessageCleanup。",
        "confirmed_difference_zh": "接收hook只验证origin和game_前缀，未在此hook验证event.source=当前iframe；quitAndGoByFrom只导航，不在这个函数调用stopMessageCleanup或清gameWindow。Stake共享wrapper退出有unsubscribe/reset的一致路径。",
        "suggestion_zh": "将当前iframe.contentWindow/sessionID校验与统一离开清理集中在壳层；确认其他入口是否已负责timer/window清理后再改，不把未发现调用写成全仓缺失。",
        "stake_reference_ids": [
          "SUP24"
        ],
        "limitations": [
          "限定本hook和quit方法，不声称存在可利用漏洞或全仓无清理。"
        ]
      },
      "headline": "退出清理和消息归属应按实际实例核验。",
      "featured": false
    },
    {
      "id": "GUI06",
      "module": "Unity资源调度/取消",
      "category": "游戏与适配",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/(game)/unity-game/UtilUnity.ts",
          "line": 19,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "67ec952502991e2be542523f63771f5311764f463f87b6417e87e7227949f633"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/unity-game/UtilUnity.ts",
          "line": 58,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "67ec952502991e2be542523f63771f5311764f463f87b6417e87e7227949f633"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/unity-game/ResourceLoader.ts",
          "line": 134,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "62880480c287ed3edf40fe78d7946dd640b7aa60ec88c3c68cafcf7d5c1f371e"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/unity-game/ResourceLoader.ts",
          "line": 415,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "62880480c287ed3edf40fe78d7946dd640b7aa60ec88c3c68cafcf7d5c1f371e"
        }
      ],
      "stake_ids": [
        "SUP03",
        "SUP24"
      ],
      "current": "ResourceLoader已有cache/version、worker回退、并发队列、请求去重、20s timeout、4xx非429不重试、dispose清worker/cache/queue。",
      "difference": "StakeVite处理模块/CSS预载失败；Unity大资源需要独立缓存并发策略，Pwa已实现，不能简单替换为Svelte import helper。UtilUnity单例Init flags与Loader dispose需要按拥有者生命周期审视。",
      "suggestion": "只验证进入/退出时谁持有资源loader并确保dispose语义一致；如全站预热应保留缓存，若游戏实例专属则离开清理。将超时/离线/HTTP错误转为统一可重试UI。",
      "limits": [
        "未核实全仓dispose调用；不会建议每次关闭清空全站长期缓存。"
      ],
      "raw": {
        "id": "GUI06",
        "module": "Unity资源调度/取消",
        "pwa_evidence": [
          {
            "source": "PwaSlotCash/src/app/(game)/unity-game/UtilUnity.ts",
            "line_1_based": 19,
            "snippet": "    static Init() {\n        if (this.isInit) return;\n        this.isInit = true;\n\n        // 初始化\n        this.resourceLoader = new ResourceLoader({\n            cacheVersion: 'v' + confJson.h5_version_name,\n            maxConcurrency: 1, //最大并发数\n            enableWorker: true,\n            enableLog: Util.isLog\n        });"
          },
          {
            "source": "PwaSlotCash/src/app/(game)/unity-game/UtilUnity.ts",
            "line_1_based": 58,
            "snippet": "        // 预加载关键资源\n        this.resourceLoader.preloadCritical([this.preloadList[0]]);\n    }\n\n    static loadUnityDataData() {\n        if (!this.isInit) return;\n        if (!this.resourceLoader) return;\n        if (this.isLoadData) return;\n        const _this = this;\n\n        this.isLoadData = true;\n\n        this.resourceLoader?.load(this.preloadList[0]);\n        this.resourceLoader?.load(this.preloadList[1]);\n\n        //加载Unity WebGL基础JS资源\n        for (let i = 0; i < this.preloadList.length; i++) {\n            const meta = this.preloadList[i];\n            // 动态加载\n            this.resourceLoader.load(meta);\n        }"
          },
          {
            "source": "PwaSlotCash/src/app/(game)/unity-game/ResourceLoader.ts",
            "line_1_based": 134,
            "snippet": "    public dispose() {\n        if (this.isDestroyed) return;\n        this.isDestroyed = true;\n\n        if (this.monitorInterval) {\n            clearInterval(this.monitorInterval);\n            this.monitorInterval = null;\n        }\n\n        this.cleanupWorker();\n        this.releaseAll();\n\n        // 清空队列\n        this.requestQueue = [];\n        this.inflightRequests.clear(); // Promise 无法取消，但我们可以丢弃引用"
          },
          {
            "source": "PwaSlotCash/src/app/(game)/unity-game/ResourceLoader.ts",
            "line_1_based": 415,
            "snippet": "        for (let i = 0; i <= maxRetries; i++) {\n            if (this.isDestroyed) throw new Error('Loader destroyed');\n\n            // 检查网络状态\n            if (typeof navigator !== 'undefined' && navigator.onLine === false) {\n                throw new Error('Network Offline');\n            }\n\n            const controller = new AbortController();\n            const timeoutId = setTimeout(() => controller.abort(), 20000); // 20s 超时\n            fetchOptions.signal = controller.signal;\n\n            try {\n                const res = await fetch(url, fetchOptions);\n                clearTimeout(timeoutId);\n\n                // 200-299 成功\n                if (res.ok) return res;\n\n                // 4xx 客户端错误 (404, 403) 通常不重试，除非是 429 (Too Many Requests)\n                if (res.status >= 400 && res.status < 500 && res.status !== 429) {\n                    // 抛出带有特定标识的错误，防止重试\n                    const err: any = new Error(`Client Error ${res.status}`);\n                    err.isFatal = true;\n                    throw err;"
          }
        ],
        "existing_strengths_zh": "ResourceLoader已有cache/version、worker回退、并发队列、请求去重、20s timeout、4xx非429不重试、dispose清worker/cache/queue。",
        "confirmed_difference_zh": "StakeVite处理模块/CSS预载失败；Unity大资源需要独立缓存并发策略，Pwa已实现，不能简单替换为Svelte import helper。UtilUnity单例Init flags与Loader dispose需要按拥有者生命周期审视。",
        "suggestion_zh": "只验证进入/退出时谁持有资源loader并确保dispose语义一致；如全站预热应保留缓存，若游戏实例专属则离开清理。将超时/离线/HTTP错误转为统一可重试UI。",
        "stake_reference_ids": [
          "SUP03",
          "SUP24"
        ],
        "limitations": [
          "未核实全仓dispose调用；不会建议每次关闭清空全站长期缓存。"
        ]
      },
      "headline": "Unity有资源调度和销毁，不能直接类比Dice。",
      "featured": false
    },
    {
      "id": "GUI07",
      "module": "移动布局与token体系",
      "category": "游戏与适配",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/styles/globals.css",
          "line": 1,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "e2bfc2d75369f15baa44fca7b68b91233fcab274f7f7ab925a89a729ad085adf"
        },
        {
          "path": "PwaSlotCash/src/app/styles/globals.css",
          "line": 19,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "e2bfc2d75369f15baa44fca7b68b91233fcab274f7f7ab925a89a729ad085adf"
        },
        {
          "path": "PwaSlotCash/src/app/styles/globals.css",
          "line": 85,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "e2bfc2d75369f15baa44fca7b68b91233fcab274f7f7ab925a89a729ad085adf"
        },
        {
          "path": "PwaSlotCash/src/app/_components/GameGrid/GameGrid.module.css",
          "line": 1,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "e789985b20ddda21f17e00b28119c05d0bc174ba72712cbfac51107db869a19b"
        },
        {
          "path": "PwaSlotCash/src/app/_components/GameHorizontalScroll/GameHorizontalScroll.module.css",
          "line": 16,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "7f0db584d7c767470f189ff216467887ab9c036278b78d41502478ca0f091fe5"
        }
      ],
      "stake_ids": [
        "SUP11",
        "SUP12",
        "SUP13",
        "SUP14"
      ],
      "current": "Pwa有750设计px→rem整体缩放、SC/GC theme变量、三列grid和横排clamp宽度；移动游戏画面风格自成体系。",
      "difference": "Stake Edge semantic tokens把surface/feedback/font/spacing/radius统一；Pwa多处组件仍裸颜色/圆角，整体根字号缩放且三列数固定，与Stake断点式应用布局不同。",
      "suggestion": "渐进补surface/text/spacing/radius/focus token并保留SC/GC品牌；宽屏需要时补独立网格列断点，先同步COLUMN_COUNT避免CSS列数与虚拟行分组不一致。",
      "limits": [
        "源码px被PostCSS转换，不能拿源码26px直接称运行时实像素。"
      ],
      "raw": {
        "id": "GUI07",
        "module": "移动布局与token体系",
        "pwa_evidence": [
          {
            "source": "PwaSlotCash/src/app/styles/globals.css",
            "line_1_based": 1,
            "snippet": ":root {\n    --background: #ffffff;\n    --foreground: #1c142b;\n    --sc-color: #44ff0f;\n    --gc-color: #ee8a08;\n    --err-color: #e32f5e;\n    --design-width: 750;\n    /* 设计基准宽度 */\n    /* fixed-px */\n    --base-font-size: 10px;\n    /* 设计基准字体大小 */\n    /* fixed-px */\n    --max-font-size: 10px;\n    /* 750 × 1624 验收时根字号为 10px，使 rem × 10 与设计稿 px 一致。 */\n    /* fixed-px */\n    --min-font-size: 2px;\n    /* 最小字体大小 */"
          },
          {
            "source": "PwaSlotCash/src/app/styles/globals.css",
            "line_1_based": 19,
            "snippet": "[data-theme=\"SC\"] {\n    --border-color: #0ed06b;\n    --text-color: #5be435;\n    --bg-color: #0ed06b;\n    --font-icon-color: #54d57d;\n    --nav-bg-color: #6ce147;\n    --mark-text-color: #5be435;\n    --circle-color: #fff000;\n    --switch-color: #2faf15;\n    --icon-color: #79d186;\n    --footer-tab-color: #0f9c5f;\n    --game-category-title: #54d57d;\n}\n[data-theme=\"GC\"] {\n    --border-color: #eeab19;\n    --mark-text-color: #ee8a08;\n    --font-icon-color: #f2c647;\n    --bg-color: #eeab19;\n    --nav-bg-color: #ffca34;\n    --circle-color: #fff000;\n    --switch-color: #ffca34;\n    --icon-color: #ecc75f;\n    --footer-tab-color: #a66e14;\n    --game-category-title: #f2c647;"
          },
          {
            "source": "PwaSlotCash/src/app/styles/globals.css",
            "line_1_based": 85,
            "snippet": "html {\n    /* 源码写 750 设计稿整数 px；PostCSS 按基数 10 转换，fixed-px 注释保护实际像素。\n       常规竖屏上限为 10px，保证 750 × 1624 下 1:1 验收；短屏和横屏保留原适配规则。 */\n    font-size: clamp(var(--min-font-size), calc(100vw / var(--design-width) * 10), var(--max-font-size));\n}"
          },
          {
            "source": "PwaSlotCash/src/app/_components/GameGrid/GameGrid.module.css",
            "line_1_based": 1,
            "snippet": ".grid,\n.virtualRow {\n    display: grid;\n    grid-template-columns: repeat(3, minmax(0, 1fr));\n    width: 100%;\n    gap: 26px;\n}"
          },
          {
            "source": "PwaSlotCash/src/app/_components/GameHorizontalScroll/GameHorizontalScroll.module.css",
            "line_1_based": 16,
            "snippet": "    display: grid;\n    grid-auto-flow: column;\n    grid-auto-columns: clamp(176px, calc((100% - 2 * 24px) / 3.75), 278px);\n    gap: 24px;"
          }
        ],
        "existing_strengths_zh": "Pwa有750设计px→rem整体缩放、SC/GC theme变量、三列grid和横排clamp宽度；移动游戏画面风格自成体系。",
        "confirmed_difference_zh": "Stake Edge semantic tokens把surface/feedback/font/spacing/radius统一；Pwa多处组件仍裸颜色/圆角，整体根字号缩放且三列数固定，与Stake断点式应用布局不同。",
        "suggestion_zh": "渐进补surface/text/spacing/radius/focus token并保留SC/GC品牌；宽屏需要时补独立网格列断点，先同步COLUMN_COUNT避免CSS列数与虚拟行分组不一致。",
        "stake_reference_ids": [
          "SUP11",
          "SUP12",
          "SUP13",
          "SUP14"
        ],
        "limitations": [
          "源码px被PostCSS转换，不能拿源码26px直接称运行时实像素。"
        ]
      },
      "headline": "适配目标不同，语义tokens可渐进引入。",
      "featured": true
    },
    {
      "id": "GUI08",
      "module": "PWA安装与safe-area",
      "category": "游戏与适配",
      "sources": [
        {
          "path": "PwaSlotCash/src/Master/PWA/KitPwa.ts",
          "line": 120,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "79dc0118b6abe87114664475da85f5fa69613cdae09eaef5fae52b8b25ff3fbd"
        },
        {
          "path": "PwaSlotCash/src/Master/PWA/KitPwa.ts",
          "line": 366,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "79dc0118b6abe87114664475da85f5fa69613cdae09eaef5fae52b8b25ff3fbd"
        },
        {
          "path": "PwaSlotCash/src/Master/PWA/KitPwa.ts",
          "line": 456,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "79dc0118b6abe87114664475da85f5fa69613cdae09eaef5fae52b8b25ff3fbd"
        },
        {
          "path": "PwaSlotCash/src/app/styles/globals.css",
          "line": 202,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "e2bfc2d75369f15baa44fca7b68b91233fcab274f7f7ab925a89a729ad085adf"
        }
      ],
      "stake_ids": [
        "SUP13",
        "SUP15"
      ],
      "current": "已有beforeinstallprompt/appinstalled、deferred prompt/userChoice、standalone/fullscreen/iOS standalone识别及top safe-area。",
      "difference": "Stake全局专门将移动底栏与standalone portrait safe-area组合，floating控件避开底栏；Pwa已可安装，但此检查只发现全局top safe-area，未证明bottom所有浮层适配。",
      "suggestion": "针对实际固定底栏/优惠/客服/游戏控制条逐一核对bottom安全区和软键盘，集中为变量，优先真机/standalone测试，别重复搭PWA安装器。",
      "limits": [
        "未检查所有CSS或运行PWA；bottom缺失仅待核查。"
      ],
      "raw": {
        "id": "GUI08",
        "module": "PWA安装与safe-area",
        "pwa_evidence": [
          {
            "source": "PwaSlotCash/src/Master/PWA/KitPwa.ts",
            "line_1_based": 120,
            "snippet": "        // 当浏览器检测到 PWA 可以安装时，会触发 beforeinstallprompt 事件\n        window.addEventListener(\n            'beforeinstallprompt',\n            this.handleBeforeInstallPrompt as EventListener\n        );\n\n        // 当用户成功安装 PWA 后，浏览器会触发 appinstalled 事件，我们可以在此事件中执行一些后续操作。\n        window.addEventListener('appinstalled', this.handleAppInstalled as EventListener);"
          },
          {
            "source": "PwaSlotCash/src/Master/PWA/KitPwa.ts",
            "line_1_based": 366,
            "snippet": "        KitPwa.deferredPrompt.prompt();\n        KitPwa.setPwaStatus(PwaStatusEnum.Installing);\n        KitPwa.deferredPrompt.userChoice.then((choiceResult) => {\n            if (choiceResult.outcome !== 'accepted') {\n                KitPwa.setPwaStatus(PwaStatusEnum.NotInstalled);"
          },
          {
            "source": "PwaSlotCash/src/Master/PWA/KitPwa.ts",
            "line_1_based": 456,
            "snippet": "        const isStandalone = window.matchMedia('(display-mode: standalone)').matches;\n        const isFullscreen = window.matchMedia('(display-mode: fullscreen)').matches;\n        const isIOSStandalone = (window.navigator as any).standalone;\n\n        if (!isStandalone && !isFullscreen && !isIOSStandalone) {"
          },
          {
            "source": "PwaSlotCash/src/app/styles/globals.css",
            "line_1_based": 202,
            "snippet": "    font-weight: 500;\n}\n.header_panel {\n    height: 138px;\n    height: max(138px, calc(98px + constant(safe-area-inset-top)));\n    height: max(138px, calc(98px + env(safe-area-inset-top)));\n}"
          }
        ],
        "existing_strengths_zh": "已有beforeinstallprompt/appinstalled、deferred prompt/userChoice、standalone/fullscreen/iOS standalone识别及top safe-area。",
        "confirmed_difference_zh": "Stake全局专门将移动底栏与standalone portrait safe-area组合，floating控件避开底栏；Pwa已可安装，但此检查只发现全局top safe-area，未证明bottom所有浮层适配。",
        "suggestion_zh": "针对实际固定底栏/优惠/客服/游戏控制条逐一核对bottom安全区和软键盘，集中为变量，优先真机/standalone测试，别重复搭PWA安装器。",
        "stake_reference_ids": [
          "SUP13",
          "SUP15"
        ],
        "limitations": [
          "未检查所有CSS或运行PWA；bottom缺失仅待核查。"
        ]
      },
      "headline": "PWA机制可直接审阅；Stake的SW实现未抓到。",
      "featured": false
    },
    {
      "id": "GUI09",
      "module": "首屏时序与无障碍反馈",
      "category": "游戏与适配",
      "sources": [
        {
          "path": "PwaSlotCash/src/Fwk/Performance/LCPGate.ts",
          "line": 5,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "6138f900ac23e449e4c755221b7905c7496228727c31349df1467e341fc786b0"
        },
        {
          "path": "PwaSlotCash/src/Fwk/Performance/LCPGate.ts",
          "line": 40,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "6138f900ac23e449e4c755221b7905c7496228727c31349df1467e341fc786b0"
        },
        {
          "path": "PwaSlotCash/src/app/styles/globals.css",
          "line": 44,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "e2bfc2d75369f15baa44fca7b68b91233fcab274f7f7ab925a89a729ad085adf"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/components/GameLoad/GameLoad.tsx",
          "line": 75,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "d66eba54a71ef8f35f9f7c9b15e375a27098dccf3c43a8f1c2d2286467edf644"
        }
      ],
      "stake_ids": [
        "SUP04",
        "SUP18"
      ],
      "current": "已有paint/stable/final三模式LCP gate，双RAF与timeout并注明stable非最终LCP；GameLoad已有role=status/aria-live。",
      "difference": "StakeCSS显式focus-visible outline与reduced-motion规则；Pwa全局outline:none，不代表所有组件无focus，但game卡render未见自补焦点。",
      "suggestion": "保留LCP gate，优先核实共享whenReady首调用mode与各任务用途；对游戏入口/收藏/加载重试添加可见键盘焦点及reduced-motion，保持loading status宣告。",
      "limits": [
        "未执行可访问性测试；全局focus问题须逐组件覆盖核验。"
      ],
      "raw": {
        "id": "GUI09",
        "module": "首屏时序与无障碍反馈",
        "pwa_evidence": [
          {
            "source": "PwaSlotCash/src/Fwk/Performance/LCPGate.ts",
            "line_1_based": 5,
            "snippet": " * 因此这里按业务对“足够晚”的不同要求提供三种模式：\n * - paint：收到首个候选后，再跨两个 animation frame 触发，适合只需避开首次绘制的任务；\n * - stable：最后一个候选静默 STABLE_MS 后触发，是默认模式，但它只是工程上的稳定启发式，\n *   不代表浏览器已经正式定稿 LCP；\n * - final：记录候选，直到首次用户输入、页面隐藏或超时后触发，适合需要接近最终值的场景。\n *\n * `whenReady()` 是全站共享门：第一次调用决定共享观测的 mode，后续调用复用同一 Promise\n * 和结果；需要不同 mode 或不能影响全局状态时必须使用 `whenReadyOnce()`。\n * 所有模式都有 FALLBACK_TIMEOUT_MS 兜底，避免浏览器不支持 PerformanceObserver、页面没有\n * 可用 LCP 候选或观察过程异常时，非关键任务永久不执行。"
          },
          {
            "source": "PwaSlotCash/src/Fwk/Performance/LCPGate.ts",
            "line_1_based": 40,
            "snippet": "const FALLBACK_TIMEOUT_MS = 6_000;\nconst STABLE_MS = 200;\nconst LOG_TAG = 'useAfterLCP';\n\n// 全站共享观测及其结果；仅 whenReady() 会读写这两个变量。\nlet readyPromise: Promise<LCPGateResult> | null = null;\nlet cachedResult: LCPGateResult | null = null;"
          },
          {
            "source": "PwaSlotCash/src/app/styles/globals.css",
            "line_1_based": 44,
            "snippet": "* {\n    -webkit-touch-callout: none;\n    /* 禁用长按菜单 */\n    -webkit-user-select: none;\n    /* 禁用文本选择 */\n    -khtml-user-select: none;\n    /* 禁用文本选择 */\n    -moz-user-select: none;\n    /* 禁用文本选择 */\n    -ms-user-select: none;\n    /* 禁用文本选择 */\n    user-select: none;\n    /* 禁用文本选择 */\n    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n    /* 禁用点击高亮效果 */\n    -webkit-tap-highlight-color: transparent;\n    /* 禁用点击高亮效果 */\n    outline: none;"
          },
          {
            "source": "PwaSlotCash/src/app/(game)/components/GameLoad/GameLoad.tsx",
            "line_1_based": 75,
            "snippet": "                <div className={styles.game_load_text} role='status' aria-label='Loading'>\n                    {Array.from(LOADING_LABEL).map((char, index) => (\n                        <span\n                            key={`${char}-${index}`}"
          }
        ],
        "existing_strengths_zh": "已有paint/stable/final三模式LCP gate，双RAF与timeout并注明stable非最终LCP；GameLoad已有role=status/aria-live。",
        "confirmed_difference_zh": "StakeCSS显式focus-visible outline与reduced-motion规则；Pwa全局outline:none，不代表所有组件无focus，但game卡render未见自补焦点。",
        "suggestion_zh": "保留LCP gate，优先核实共享whenReady首调用mode与各任务用途；对游戏入口/收藏/加载重试添加可见键盘焦点及reduced-motion，保持loading status宣告。",
        "stake_reference_ids": [
          "SUP04",
          "SUP18"
        ],
        "limitations": [
          "未执行可访问性测试；全局focus问题须逐组件覆盖核验。"
        ]
      },
      "headline": "已有首屏门控，可补焦点与进度反馈。",
      "featured": false
    },
    {
      "id": "PD03",
      "module": "会话恢复与账户切换",
      "category": "状态与数据",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/hooks/useLoginInit.ts",
          "line": 52,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 66,
          "sha256": "b85dad45e2a4ab0e6a05bede122cd55b025c55254aa065ca900574fd5e947169"
        },
        {
          "path": "PwaSlotCash/src/app/hooks/useLoginInit.ts",
          "line": 62,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 66,
          "sha256": "b85dad45e2a4ab0e6a05bede122cd55b025c55254aa065ca900574fd5e947169"
        },
        {
          "path": "PwaSlotCash/src/app/hooks/useRefreshToken.tsx",
          "line": 25,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 36,
          "sha256": "7458bba62d577062c1eee5fe2b3f8625a2b45abd4da83fa323fd4571eef8d4c2"
        },
        {
          "path": "PwaSlotCash/src/app/hooks/useRefreshToken.tsx",
          "line": 40,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 46,
          "sha256": "7458bba62d577062c1eee5fe2b3f8625a2b45abd4da83fa323fd4571eef8d4c2"
        },
        {
          "path": "PwaSlotCash/src/Fwk/Http/Interceptors/AuthInterceptor.ts",
          "line": 16,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 18,
          "sha256": "4c1299586a7c9da84cf6863630662d48cf1e6740ac8a57d370b2eb8256a39c0d"
        }
      ],
      "stake_ids": [
        "API-06",
        "API-07",
        "L12"
      ],
      "current": "useLoginInit按OAuth首次回调→本地token恢复Redux并拉用户→无token访客登录的优先级；OAuth code有sessionStorage防重复。useRefreshToken仅登录且未logout时每5分钟刷新，成功更新本地token/Redux，失败广播expired。AuthInterceptor按isToken添加Authorization。",
      "difference": "Stake公开client在authenticatedQuery无token时abort，shell区分匿名/登录；Pwa拥有真正访客身份，不能照抄成Stake的“未登录禁用全部入口”。Pwa的用户/游客区别与币种开关要维持。",
      "suggestion": "把会话恢复过程呈现为单一auth状态：restoring/visitor/authenticated/expired，避免header显示游客而余额区仍显示上个账户的短暂矛盾。 账户切换时使用户数据响应带身份代次；可以复用authenticatedBaseConfig已有getUserId旧响应判断方式，扩到用户与奖励首包。",
      "limits": [
        "只读源码，没有实际token或cookie读取。",
        "旧Core/UserGlobalMgr.hasTokenOk等已注释空实现未作为有效主链证据；主链以useLoginInit/Redux为准。"
      ],
      "raw": {
        "id": "PD03",
        "module": "会话恢复与账户切换",
        "pwa_current": "useLoginInit按OAuth首次回调→本地token恢复Redux并拉用户→无token访客登录的优先级；OAuth code有sessionStorage防重复。useRefreshToken仅登录且未logout时每5分钟刷新，成功更新本地token/Redux，失败广播expired。AuthInterceptor按isToken添加Authorization。",
        "pwa_evidence": [
          {
            "file": "PwaSlotCash/src/app/hooks/useLoginInit.ts",
            "line": 52,
            "end_line": 66,
            "snippet": "    // 负责三件事（按优先级顺序）：\n    //   1. OAuth 回调处理（Google/Facebook/Apple code+state）\n    //   2. 本地 token 同步到 Redux + 拉取用户信息\n    //   3. 无 token 时发起访客登录\n    const onRouterChange = useMemoizedFn(async (path: string) => {\n        const code = searchParams.get('code');\n        const state = searchParams.get('state');\n        const error = searchParams.get('error') || undefined;\n\n        // 防重放：同一 code 只处理一次，避免 Next.js 路由复用时二次执行\n        const isCodeProcessed = code\n            ? typeof window !== 'undefined' &&\n              sessionStorage.getItem(`social_auth_processed_${code}`) === '1'\n            : false;\n"
          },
          {
            "file": "PwaSlotCash/src/app/hooks/useLoginInit.ts",
            "line": 62,
            "end_line": 66,
            "snippet": "        const isCodeProcessed = code\n            ? typeof window !== 'undefined' &&\n              sessionStorage.getItem(`social_auth_processed_${code}`) === '1'\n            : false;\n"
          },
          {
            "file": "PwaSlotCash/src/app/hooks/useRefreshToken.tsx",
            "line": 25,
            "end_line": 36,
            "snippet": "    const refreshToken = async (isSerErrTips = true) => {\n        const response = await $http.Auth.userRefreshToken({ isSerErrTips });\n        if (response && response.token) {\n            setToken(response.token);\n            dispatch(SET_ACCESS_TOKEN(response.token));\n        } else {\n            tabPostMessage({\n                type: GameMsgKey.game_account_token_expired,\n            });\n        }\n    };\n"
          },
          {
            "file": "PwaSlotCash/src/app/hooks/useRefreshToken.tsx",
            "line": 40,
            "end_line": 46,
            "snippet": "    useInterval(\n        () => refreshToken(false),\n        userInfo.isLoginOk && !userInfo.isLoggedOut\n            ? options.delay || 1000 * 60 * 5 // 默认5分钟\n            : undefined, // 停止定时器\n    );\n}"
          },
          {
            "file": "PwaSlotCash/src/Fwk/Http/Interceptors/AuthInterceptor.ts",
            "line": 16,
            "end_line": 18,
            "snippet": "    if (config.isToken) {\n        config.headers.Authorization = getToken();\n        //检测所有接口增加上默认token"
          }
        ],
        "stake_evidence": [
          {
            "id": "API-06",
            "ledger": "work/stake-backend-notes.json",
            "claim": "setSessionHeaders从session.session添加x-access-token，locale转x-language；pt转br。此分析仅读取字段名和源码逻辑，不读取任何认证值。",
            "source": {
              "file": "outputs/stake-us-source/chunks/Cu3B9wdT.mjs",
              "character_offset_0based": 23291,
              "end_character_offset_0based_exclusive": 23591,
              "line": 30,
              "snippet": "function be({request:e,session:t}){let n={};t?.session&&(n[`x-access-token`]=t.session),t&&(n[`x-language`]=G(t.locale));let r=J(e,n);return{...e,headers:r}}var X=async({request:e,doc:t})=>{let n=t?.definitions?.[0],r=J(e,{\"x-operation-name\":(`name`in n?n.name?.value:void 0)||`unknown`,\"x-operation-"
            }
          },
          {
            "id": "API-07",
            "ledger": "work/stake-backend-notes.json",
            "claim": "Ae在现有requestInterceptors后增加检查，没有x-access-token时controller.abort后返回request；query发现signal.aborted就返回aborted networkError，不向网络发请求。",
            "source": {
              "file": "outputs/stake-us-source/chunks/Cu3B9wdT.mjs",
              "character_offset_0based": 32639,
              "end_character_offset_0based_exclusive": 33069,
              "line": 30,
              "snippet": "function Ae(e){let t=async({request:e,controller:t})=>(new Headers(e.headers).has(`x-access-token`)===!1&&t.abort(`Request aborted: missing authentication token for authenticated query.`),e);return Q().query({...e,requestInterceptors:[...Q().getRequestInterceptors(),t]})}function je(e){return Q().fetchGraphQL(e)}function Me(e,t,n){return Q().mutation(e,t,n)}function Ne(e,t,n){return Q().mutate(e,t,n)}function Pe(e){Q().setUrl("
            }
          },
          {
            "id": "L12",
            "ledger": "work/stake-layout-deep.json",
            "claim": "访客首页与登录首页走不同首屏分支",
            "source": {
              "path": "outputs/stake-us-source/nodes/55.BHHpgWjN.mjs",
              "sha256": "4f9f8c771b2819d05c92cfafa92edc7062bdff5cca81877c633445cb9759162f",
              "line": 2,
              "character_start": 35950,
              "character_end": 37280,
              "byte_start": 35950,
              "snippet": "function yn(e,n){j(n,!0);let r=()=>k(U,`$isSportsbookEnabledForRegion`,a),i=()=>k(v,`$meta`,a),[a,o]=u(),c=b(n,`heroes`,19,()=>[]),h=b(n,`promotions`,19,()=>[]),_=b(n,`personalisedGames`,3,null),{meta:v}=le(),y=g(()=>[...n.trendingGames?.cards??[],...n.trendingSports?.cards??[]].length===0),x=g(r);var S=t(),w=d(S);m(w,()=>$e,(e,r)=>{r(e,{children:(e,r)=>{var a=t(),o=d(a);m(o,()=>Ce,(e,r)=>{r(e,{class:`pb-[unset]`,children:(e,r)=>{var a=vn(),o=d(a),u=e=>{nn(e,{get isSportsbookEnabled(){return O(x)},get vipProgress(){return n.vipProgress}})},v=e=>{pn(e,{get isSportsbookEnabled(){return O(x)}})};s(o,e=>{i().isAuthenticated?e(u):e(v,-1)});var b=f(o,2);m(b,()=>Se,(e,r)=>{r(e,{padding:!0,children:(e,r)=>{var a=_n(),o=d(a);m(o,()=>nt,(e,t)=>{t(e,{get loading(){return O(y)},get trendingGames(){return n.trendingGames},get personalisedGames(){return _()},get userRecentGames(){return n.userRecentGames}})});var u=f(o,2);m(u,()=>tt,(e,t)=>{t(e,{get loading(){return O(y)},get list(){return n.trendingSports}})});var v=f(u,2),b=e=>{var n=t(),r=d(n),i=e=>{X(e,{children:(e,n)=>{let r=()=>(n?.()).visible;var i=t(),a=d(i),o=e=>{var n=t(),r=d(n);l(r,()=>F(()=>import(`../chunks/CUeUO4t2.mjs`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,4"
            }
          }
        ],
        "comparison": "Stake公开client在authenticatedQuery无token时abort，shell区分匿名/登录；Pwa拥有真正访客身份，不能照抄成Stake的“未登录禁用全部入口”。Pwa的用户/游客区别与币种开关要维持。",
        "frontend_changes": [
          "把会话恢复过程呈现为单一auth状态：restoring/visitor/authenticated/expired，避免header显示游客而余额区仍显示上个账户的短暂矛盾。",
          "账户切换时使用户数据响应带身份代次；可以复用authenticatedBaseConfig已有getUserId旧响应判断方式，扩到用户与奖励首包。"
        ],
        "unknowns": [
          "只读源码，没有实际token或cookie读取。",
          "旧Core/UserGlobalMgr.hasTokenOk等已注释空实现未作为有效主链证据；主链以useLoginInit/Redux为准。"
        ]
      },
      "headline": "KYC旧响应守卫可推广到其他账户数据。",
      "featured": false
    },
    {
      "id": "PD04",
      "module": "GC／SC切换和余额组件",
      "category": "状态与数据",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/userSlice.ts",
          "line": 39,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 41,
          "sha256": "549e6a68a42b41685fe58179198c42ea593535c0e91674f14a82e773435445a2"
        },
        {
          "path": "PwaSlotCash/src/app/_components/Coin/CoinLabel.tsx",
          "line": 29,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 41,
          "sha256": "acbd7b2d4a69cae33dfd91a686af573be3ff16e2a2fa33bdc043ee15c6f64412"
        },
        {
          "path": "PwaSlotCash/src/app/_components/Coin/CoinLabel.tsx",
          "line": 61,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 74,
          "sha256": "acbd7b2d4a69cae33dfd91a686af573be3ff16e2a2fa33bdc043ee15c6f64412"
        },
        {
          "path": "PwaSlotCash/src/Master/DriveUtil.ts",
          "line": 28,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 30,
          "sha256": "6d88ec1ee3021d111020625e0fdfa29891e0683efade1e6d5b02ba80eed0e252"
        },
        {
          "path": "PwaSlotCash/src/Core/Login/types/user.ts",
          "line": 8,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 21,
          "sha256": "e78b55c61f12637efcffaa7402d9d9c856b8c72744caed91cce3cf6097fac9ff"
        }
      ],
      "stake_ids": [
        "D24",
        "D26"
      ],
      "current": "RewardType是Redux共享选择，CoinLabel直接选userInfo.rewardType显示GC或SC；SC读convertTotalScAmount并经ToNormalUnit转显示单位。BaseInfo分可兑换SC、赠送SC与total，内部万分位。GC直接显示gcAmount，NumberScroll负责动画。",
      "difference": "Stake选择币种会STOP_AUTOBETTING并仅idle同步游戏币；Pwa承载Unity/iframe游戏，币种消息是否允许中途切换取决于游戏协议，不应机械移植停止自动局。可以借鉴全局selected currency与消费组件单一来源、金额校验独立派生。",
      "suggestion": "建立selectDisplayedBalance(rewardType)和selectRedeemableSc这类细粒度selector，Header/CoinLabel/Redeem复用同样单位转换，避免每个组件读取整个userInfo重复计算。 币种切换先显示稳定选中态与单位；正在启动游戏时锁定本次launch的rewardType，确认游戏桥支持切币后再传同步消息。",
      "limits": [
        "未确认所有游戏支持运行中GC/SC切换。",
        "SC内部万分位是Pwa业务合同；Stakecurrency数字不能直接拿来替换Pwa单位。"
      ],
      "raw": {
        "id": "PD04",
        "module": "GC／SC切换和余额组件",
        "pwa_current": "RewardType是Redux共享选择，CoinLabel直接选userInfo.rewardType显示GC或SC；SC读convertTotalScAmount并经ToNormalUnit转显示单位。BaseInfo分可兑换SC、赠送SC与total，内部万分位。GC直接显示gcAmount，NumberScroll负责动画。",
        "pwa_evidence": [
          {
            "file": "PwaSlotCash/src/app/userSlice.ts",
            "line": 39,
            "end_line": 41,
            "snippet": "        SET_REWARD_TYPE: (state, action) => {\n            state.rewardType = action.payload;\n        },"
          },
          {
            "file": "PwaSlotCash/src/app/_components/Coin/CoinLabel.tsx",
            "line": 29,
            "end_line": 41,
            "snippet": "    const scCoin = React.useMemo(() => {\n        const scAmount = userInfo.convertTotalScAmount;\n        return DriveUtil.ToNormalUnit(scAmount) || 0;\n    }, [userInfo]);\n    // const isSCCoin = React.useMemo(\n    //     () => {\n    //         return userInfo.rewardType === RewardType.SC;\n    //     },\n    //     [userInfo]\n    // );\n\n    const isSCCoin = userInfo.rewardType === RewardType.SC;\n"
          },
          {
            "file": "PwaSlotCash/src/app/_components/Coin/CoinLabel.tsx",
            "line": 61,
            "end_line": 74,
            "snippet": "                {isSCCoin ? 'SC' : 'GC'}\n            </div>\n            <div ref={coinTextRef} className={styles.coin}>\n                {\n                    isSCCoin ? (\n                        <NumberScroll value={scCoin} type='scCoin' />\n                    ) : (\n                        // Number(DriveUtil.ToScString(scCoin, true, 4))\n                        <NumberScroll value={userInfo.gcAmount} type='gcCoin' />\n                    )\n                    // Number(DriveUtil.ToGcString(userInfo.gcAmount || 0))\n                }\n            </div>\n            <div"
          },
          {
            "file": "PwaSlotCash/src/Master/DriveUtil.ts",
            "line": 28,
            "end_line": 30,
            "snippet": "    public static ToNormalUnit(count: number): number {\n        return Math.round(count / 10000 * 10000) / 10000;\n    }"
          },
          {
            "file": "PwaSlotCash/src/Core/Login/types/user.ts",
            "line": 8,
            "end_line": 21,
            "snippet": "    //可提现sc余额\n    availableScAmount: number = 0;\n    //可提现sc余额(万分位)\n    convertAvailableScAmount: number = 0;\n    //赠送sc余额(不可提现)(万分位)\n    convertRewardScAmount: number = 0;\n    //scAmountAtom this.convertAvailableScAmount + this.convertRewardScAmount;\n    //总的SC显示的呢\n    convertTotalScAmount: number = 0;\n    //游戏经验\n    gameExp: number = 0;\n    //游戏等级\n    gameLevel: number = 0;\n    //gc余额"
          }
        ],
        "stake_evidence": [
          {
            "id": "D24",
            "ledger": "work/stake-dice-deep.json",
            "claim": "变更交易币触发STOP_AUTOBETTING；仅idle同步游戏currency。法币模式 inPlay且auto/advanced，conversion变化>10%或新rate===9时停止并显示warn。rate===9的来源/语义尚未解释。",
            "source": "outputs/stake-us-source/chunks/DYosYWTC.mjs"
          },
          {
            "id": "D26",
            "ledger": "work/stake-dice-deep.json",
            "claim": "general store使idle之外disabled/inPlay，amount校验min0/balance；initial snapshot setter包装{gameState,amount,chips}，便于策略resetAmount/resetWinChance。amount的double/max取可用余额，支持halve。",
            "source": "outputs/stake-us-source/chunks/ri_ajtQq.mjs"
          }
        ],
        "comparison": "Stake选择币种会STOP_AUTOBETTING并仅idle同步游戏币；Pwa承载Unity/iframe游戏，币种消息是否允许中途切换取决于游戏协议，不应机械移植停止自动局。可以借鉴全局selected currency与消费组件单一来源、金额校验独立派生。",
        "frontend_changes": [
          "建立selectDisplayedBalance(rewardType)和selectRedeemableSc这类细粒度selector，Header/CoinLabel/Redeem复用同样单位转换，避免每个组件读取整个userInfo重复计算。",
          "币种切换先显示稳定选中态与单位；正在启动游戏时锁定本次launch的rewardType，确认游戏桥支持切币后再传同步消息。"
        ],
        "unknowns": [
          "未确认所有游戏支持运行中GC/SC切换。",
          "SC内部万分位是Pwa业务合同；Stakecurrency数字不能直接拿来替换Pwa单位。"
        ]
      },
      "headline": "GC/SC是业务币种，保留现有数值语义。",
      "featured": true
    },
    {
      "id": "PD05",
      "module": "余额快照、更新与展示确认",
      "category": "状态与数据",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/userSliceAPI.ts",
          "line": 833,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 844,
          "sha256": "e74b7356ee3b2fc7ff254008cded218dfa0b99e477ff5b703eaae97b8b78756f"
        },
        {
          "path": "PwaSlotCash/src/app/userSliceExtraReducer.ts",
          "line": 112,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 141,
          "sha256": "b03bc9d3ffbbd0d3ff5f8db5bf06e349181e22a337e2848c2a5f724e011b044f"
        },
        {
          "path": "PwaSlotCash/src/app/_api/account.ts",
          "line": 13,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 26,
          "sha256": "496197cf94ebab53ce252365113992a951ba33529c910d9ae5220e51adc619ec"
        }
      ],
      "stake_ids": [
        "D19",
        "D22",
        "D23"
      ],
      "current": "Pwa从getUserInfoByBaseInfo请求Account.getUserInfo，经fulfilled reducer写balanceVersion及GC/SC快照；reducer没有在这段比较balanceVersion大小。GMV购买时暂不覆盖totalSC和GC，但可兑换/赠送等字段照常更新。getUserInfo调用1.5s节流以共享返回。",
      "difference": "Stake使用按identifier的UI余额ledger关联HTTP返回、WS余额、RAW_SYNC、GAME_FINISHED与ROLLBACK，Pwa目前这条链是刷新完整用户快照。不能据此说Pwa没有后端流水，或要求照搬Stake ledger；对照只针对浏览器余额显示。",
      "suggestion": "充值/领奖结束后，余额区显示“更新中”到权威快照落下；动画用前值→确认值，不要拿领奖显示金额与刷新快照做两次永久加法。 在当前余额reducer增加身份/请求序号校验，并在服务合同确认balanceVersion单调后再用version忽略旧快照。 把GMV中的暂存显示与真实余额字段区分为pendingDisplay层，退出GMV流程后统一reconcile，避免同一header出现总SC与可兑换SC更新时刻不一致。",
      "limits": [
        "余额版本是否在服务器各端严格单调未知；不能未经确认就采用version过滤。",
        "Pwa是否其他游戏中有增量余额更新未在这条快照链上确认；未将此对照当账务正确性结论。"
      ],
      "raw": {
        "id": "PD05",
        "module": "余额快照、更新与展示确认",
        "pwa_current": "Pwa从getUserInfoByBaseInfo请求Account.getUserInfo，经fulfilled reducer写balanceVersion及GC/SC快照；reducer没有在这段比较balanceVersion大小。GMV购买时暂不覆盖totalSC和GC，但可兑换/赠送等字段照常更新。getUserInfo调用1.5s节流以共享返回。",
        "pwa_evidence": [
          {
            "file": "PwaSlotCash/src/app/userSliceAPI.ts",
            "line": 833,
            "end_line": 844,
            "snippet": "export const getUserInfoByBaseInfo = createAsyncThunk(\n    'user/getUserInfoByBaseInfo',\n    async (params, ThunkApi) => {\n        const response = await $http.Account.getUserInfo();\n\n        // 获取会员基础信息\n        const userParams = {\n            vipExp: response.baseInfo.vipExp,\n            vipLevel: response.baseInfo.vipLevel\n        };\n        ThunkApi.dispatch(updateAccountAsync(userParams));\n"
          },
          {
            "file": "PwaSlotCash/src/app/userSliceExtraReducer.ts",
            "line": 112,
            "end_line": 141,
            "snippet": "export function getUserBaseInfoAsyncExtraReducer(\n    state: WritableDraft<UserInfoState>,\n    action: PayloadAction<\n        UserInfoResponse,\n        string,\n        { arg: any; requestId: string; requestStatus: 'fulfilled' },\n        never\n    >\n) {\n    state.requestStatus = 'idle';\n    state.requestStatusCode = RequestStatusEnum.idle;\n    state.availableScAmount = action.payload.baseInfo.availableScAmount;\n    state.balanceVersion = action.payload.baseInfo.balanceVersion;\n    state.convertAvailableScAmount = action.payload.baseInfo.convertAvailableScAmount;\n    state.convertRewardScAmount = action.payload.baseInfo.convertRewardScAmount;\n    // 特殊处理gmv支付场景, 如果处于GMV礼包支付流程先不更新账户信息\n    const gmvBuying = useGmvDataStore.getState().gmvBuying;\n    if (Util.isLog) {\n        LogUtil.info('gmvBuying', gmvBuying);\n    }\n    if (!gmvBuying) {\n        state.convertTotalScAmount = action.payload.baseInfo.convertTotalScAmount;\n        state.gcAmount = action.payload.baseInfo.gcAmount;\n    }\n\n    state.gameExp = action.payload.baseInfo.gameExp;\n    state.gameLevel = action.payload.baseInfo.gameLevel;\n    state.rewardScAmount = action.payload.baseInfo.rewardScAmount;\n    state.vipExp = action.payload.baseInfo.vipExp;\n    state.vipLevel = action.payload.baseInfo.vipLevel;"
          },
          {
            "file": "PwaSlotCash/src/app/_api/account.ts",
            "line": 13,
            "end_line": 26,
            "snippet": "export async function getUserInfo(\n    params: Record<string, any> = {}\n): Promise<API_ACCOUNT.UserInfoResponse> {\n    params.userId = getUserId();\n    const config: HttpConfig = {\n        url: RequestMgr.getUrl(NetType.GetUserInitInfo),\n        method: 'POST',\n        body: params,\n        throttle: 1500 // 1.5秒节流\n    };\n    const response = await DoSendRequest<API_ACCOUNT.UserInfoResponse>(config);\n    response.data.timestamp = response.timestamp;\n    return response.data;\n}"
          }
        ],
        "stake_evidence": [
          {
            "id": "D19",
            "ledger": "work/stake-dice-deep.json",
            "claim": "成功用返回amount/payout/currency做 optimisticBalanceUpdate；异常使用identifier rollback再reject。create 的amount为0，因此不能称Dice此处请求前就按实际下注金额扣款。finishGame稍后补GAME_FINISHED事件。",
            "source": "outputs/stake-us-source/chunks/B_cV99S-.mjs"
          },
          {
            "id": "D22",
            "ledger": "work/stake-dice-deep.json",
            "claim": "余额 ledger 按 identifier 管INITIAL_DEDUCTION/OPTIMISTIC/RAW_SYNC/WS_UPDATE/GAME_FINISHED/ROLLBACK；ROLLBACK使记录清除。结束+WS_UPDATE，或结束+optimistic+后续raw sync等条件亦清除。展示余额由raw balance减未清ledger deduction派生，非直接覆盖单一amount。",
            "source": "outputs/stake-us-source/chunks/DruF6Kad.mjs"
          },
          {
            "id": "D23",
            "ledger": "work/stake-dice-deep.json",
            "claim": "create生成identifier并登记INITIAL_DEDUCTION；rollback登记ROLLBACK；finishGame登记GAME_FINISHED。以这些事件协调请求与WebSocket/拉取原始余额的不同先后顺序。",
            "source": "outputs/stake-us-source/chunks/DruF6Kad.mjs"
          }
        ],
        "comparison": "Stake使用按identifier的UI余额ledger关联HTTP返回、WS余额、RAW_SYNC、GAME_FINISHED与ROLLBACK，Pwa目前这条链是刷新完整用户快照。不能据此说Pwa没有后端流水，或要求照搬Stake ledger；对照只针对浏览器余额显示。",
        "frontend_changes": [
          "充值/领奖结束后，余额区显示“更新中”到权威快照落下；动画用前值→确认值，不要拿领奖显示金额与刷新快照做两次永久加法。",
          "在当前余额reducer增加身份/请求序号校验，并在服务合同确认balanceVersion单调后再用version忽略旧快照。",
          "把GMV中的暂存显示与真实余额字段区分为pendingDisplay层，退出GMV流程后统一reconcile，避免同一header出现总SC与可兑换SC更新时刻不一致。"
        ],
        "unknowns": [
          "余额版本是否在服务器各端严格单调未知；不能未经确认就采用version过滤。",
          "Pwa是否其他游戏中有增量余额更新未在这条快照链上确认；未将此对照当账务正确性结论。"
        ]
      },
      "headline": "余额快照与乐观账本解决不同场景。",
      "featured": true
    },
    {
      "id": "PD06",
      "module": "跨标签通知与回到大厅刷新",
      "category": "状态与数据",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/context/UserContext.tsx",
          "line": 89,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 106,
          "sha256": "809715845dd348388e7e1ad67e5de65324c64942c45208b5a34183295a0bf353"
        },
        {
          "path": "PwaSlotCash/src/app/context/UserContext.tsx",
          "line": 111,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 116,
          "sha256": "809715845dd348388e7e1ad67e5de65324c64942c45208b5a34183295a0bf353"
        },
        {
          "path": "PwaSlotCash/src/app/hooks/useBroadcastChannel.tsx",
          "line": 56,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 60,
          "sha256": "c562dab0cc3774a3a830ec8c26019ef9677390559fdc464953100c2bfa42e06a"
        },
        {
          "path": "PwaSlotCash/src/app/hooks/useBroadcastChannel.tsx",
          "line": 74,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 86,
          "sha256": "c562dab0cc3774a3a830ec8c26019ef9677390559fdc464953100c2bfa42e06a"
        }
      ],
      "stake_ids": [
        "API-16",
        "API-17",
        "API-18",
        "API-19",
        "D22"
      ],
      "current": "UserContext从NOT_AUTH_FOOTER_PAGES回流时HandleUnityGameData后dispatch基础信息刷新；非游戏页用户requestStatus idle时广播game_refresh_data。useBroadcastChannel使用同源global-notifications，加tabId/timestamp，isNotCurTab时过滤自身和旧消息，并卸载监听。不是服务器实时余额订阅。",
      "difference": "Stake有graphql-transport-ws服务器订阅；Pwa已确认的是本地BroadcastChannel+路由回流fetch。全src静态搜索WebSocket/EventSource未找到直接构造，不足以排除依赖SDK/game包隐藏连接。两者解决不同层问题，不是BroadcastChannel能替代WS。",
      "suggestion": "把“页面重新可见、从游戏回来、其他标签有充值/领奖完成”统一为invalidateBalance事件，再由一个刷新入口合并请求。 在余额区记录lastConfirmedAt与refreshing，后台页面恢复时保留旧值并静默核对，而非清零后重绘。",
      "limits": [
        "依赖SDK或机台内的socket不在此源码搜索范围内；后端是否有可订阅服务未知。",
        "没有必要为了模仿Stake新增WS后端；先完善现有刷新事件与组件反馈即可。"
      ],
      "raw": {
        "id": "PD06",
        "module": "跨标签通知与回到大厅刷新",
        "pwa_current": "UserContext从NOT_AUTH_FOOTER_PAGES回流时HandleUnityGameData后dispatch基础信息刷新；非游戏页用户requestStatus idle时广播game_refresh_data。useBroadcastChannel使用同源global-notifications，加tabId/timestamp，isNotCurTab时过滤自身和旧消息，并卸载监听。不是服务器实时余额订阅。",
        "pwa_evidence": [
          {
            "file": "PwaSlotCash/src/app/context/UserContext.tsx",
            "line": 89,
            "end_line": 106,
            "snippet": "    // ==================== 机台回流数据同步 ====================\n    useEffect(() => {\n        if (!userInfo.isGuide) return;\n\n        const prevPath = prevPathRef.current;\n        prevPathRef.current = pathName;\n\n        if (NOT_AUTH_FOOTER_PAGES.includes(prevPath)) {\n            UnityGameDataManager.HandleUnityGameData(() => {\n                dispatch(getUserInfoByBaseInfo()).then(() => {\n                    if (Util.isLog) {\n                        LogUtil.info('Machine slot data loaded successfully:');\n                    }\n                });\n            });\n        }\n    }, [userInfo.isGuide, shopItem.shopItemsStarterData.length, pathName]);\n"
          },
          {
            "file": "PwaSlotCash/src/app/context/UserContext.tsx",
            "line": 111,
            "end_line": 116,
            "snippet": "        tabPostMessage({\n            type: GameMsgKey.game_refresh_data,\n            payload: 'UserInfoAsync',\n        });\n\n        if (userInfo.vipLevel > 0) {"
          },
          {
            "file": "PwaSlotCash/src/app/hooks/useBroadcastChannel.tsx",
            "line": 56,
            "end_line": 60,
            "snippet": "            const isSelfMessage = fromTabId === tabId;\n            const lastHandled = getLastBroadcastTime();\n            const isOldMsg = timestamp <= lastHandled;\n\n            if (!onMessage) {"
          },
          {
            "file": "PwaSlotCash/src/app/hooks/useBroadcastChannel.tsx",
            "line": 74,
            "end_line": 86,
            "snippet": "            if (isNotCurTab) {\n                if (isSelfMessage || isOldMsg) return;\n                setLastBroadcastTime(timestamp);\n                if (Util.isLog) {\n                    LogUtil.success('Broadcast', `Message received`, event.data, onMessage);\n                }\n                onMessage({ ...event.data, timestamp });\n            } else {\n                onMessage(event.data);\n            }\n        };\n\n        const channel = globalChannel();"
          }
        ],
        "stake_evidence": [
          {
            "id": "API-16",
            "ledger": "work/stake-backend-notes.json",
            "claim": "pe使用localhost匹配选ws，否则wss，路径/_api/websockets。连接子协议graphql-transport-ws；connection_init payload包含accessToken/language字段。",
            "source": {
              "file": "outputs/stake-us-source/chunks/Cu3B9wdT.mjs",
              "character_offset_0based": 22314,
              "end_character_offset_0based_exclusive": 22734,
              "line": 30,
              "snippet": "var pe=()=>{let e=window.location.hostname;return`${/localhost$/i.test(e)?`ws`:`wss`}://${e}/_api/websockets`},me=()=>{let e=t(r)?.data?.session?.locale;return n.supportedLanguages.includes(e)?e:n.defaultLanguage},he=async e=>{let t=Math.min(1e3*1.5**e,1e4),n=t/2+t/2*Math.random();await i(n)},ge=()=>({accessToken:a()??void 0,language:G(me())});function J(e,t={}){let n=new Headers;e.headers instanceof Headers&&e.heade"
            }
          },
          {
            "id": "API-17",
            "ledger": "work/stake-backend-notes.json",
            "claim": "底层graphql-ws默认retryAttempts5，但业务createWebSocketClient明确覆盖200、shouldRetry()=>true、retryWait he。he delay=min(1000*1.5^n,10000)/2 + 后半随机，范围为上限的一半至上限。",
            "source": {
              "file": "outputs/stake-us-source/chunks/Cu3B9wdT.mjs",
              "character_offset_0based": 24245,
              "end_character_offset_0based_exclusive": 24615,
              "line": 30,
              "snippet": "createWebSocketClient(){let e,t,n,r=!1;return ae({url:this.websocketUrl,retryAttempts:200,shouldRetry:()=>!0,retryWait:he,connectionParams:async()=>(r&&(r=!1,await i(100)),ge()),keepAlive:1e4,on:{opened:t=>{e=t,this.socketEmitter?.next({type:`opened`})},error:()=>this.socketEmitter?.next({type:`error`}),connecting:()=>this.socketEmitter?.next({type:`connecting`}),conn"
            }
          },
          {
            "id": "API-18",
            "ledger": "work/stake-backend-notes.json",
            "claim": "keepAlive10s；发ping后4s无pong关闭4408；接pong清timer。restart事件主动以Forbidden关闭，下一连接延迟100ms并更新connectionParams；这不是服务端源码。",
            "source": {
              "file": "outputs/stake-us-source/chunks/Cu3B9wdT.mjs",
              "character_offset_0based": 24611,
              "end_character_offset_0based_exclusive": 25251,
              "line": 30,
              "snippet": "connected:e=>{this.socketEmitter?.next({type:`connected`}),n&&n(),n=this.socketEmitter?.subscribe({restart:()=>{r=!0,e.readyState===WebSocket.OPEN&&e.close(z.Forbidden,`Forbidden`)}})},closed:e=>{this.socketEmitter?.next({type:`closed`}),typeof e==`object`&&e&&`code`in e&&e.code===z.Forbidden&&(r=!0)},ping:n=>{n||(t&&clearTimeout(t),t=setTimeout(()=>{e?.readyState===WebSocket.OPEN&&e.close(4408,`Request Timeout`)},4e3))},pong:e=>{e&&clearTimeout(t)}}})}async query(e){let{doc:t,variables:n,cache:r,logger:i=this.logger,requestInterceptors:a=this.requestInterceptors,graphQLErrorInterceptors:o=this.graphQLErrorInterceptors,networkErrorI"
            }
          },
          {
            "id": "API-19",
            "ledger": "work/stake-backend-notes.json",
            "claim": "graphql-ws subscribe返回unsubscribe，OPEN连接时发送complete(id)，减订阅引用数并释放；lazy连接且无订阅时底层可关闭。调用者要在生命周期清理返回handle。",
            "source": {
              "file": "outputs/stake-us-source/chunks/Cu3B9wdT.mjs",
              "character_offset_0based": 19765,
              "end_character_offset_0based_exclusive": 19950,
              "line": 30,
              "snippet": "a=()=>{!r&&o.readyState===v.OPEN&&o.send(V({id:n,type:B.Complete},h)),S--,r=!0,s()},await c.finally(l);return}catch(e){if(!O(e))return}})().then(()=>{i||t.complete()}).catch(e=>{t.error"
            }
          },
          {
            "id": "D22",
            "ledger": "work/stake-dice-deep.json",
            "claim": "余额 ledger 按 identifier 管INITIAL_DEDUCTION/OPTIMISTIC/RAW_SYNC/WS_UPDATE/GAME_FINISHED/ROLLBACK；ROLLBACK使记录清除。结束+WS_UPDATE，或结束+optimistic+后续raw sync等条件亦清除。展示余额由raw balance减未清ledger deduction派生，非直接覆盖单一amount。",
            "source": "outputs/stake-us-source/chunks/DruF6Kad.mjs"
          }
        ],
        "comparison": "Stake有graphql-transport-ws服务器订阅；Pwa已确认的是本地BroadcastChannel+路由回流fetch。全src静态搜索WebSocket/EventSource未找到直接构造，不足以排除依赖SDK/game包隐藏连接。两者解决不同层问题，不是BroadcastChannel能替代WS。",
        "frontend_changes": [
          "把“页面重新可见、从游戏回来、其他标签有充值/领奖完成”统一为invalidateBalance事件，再由一个刷新入口合并请求。",
          "在余额区记录lastConfirmedAt与refreshing，后台页面恢复时保留旧值并静默核对，而非清零后重绘。"
        ],
        "unknowns": [
          "依赖SDK或机台内的socket不在此源码搜索范围内；后端是否有可订阅服务未知。",
          "没有必要为了模仿Stake新增WS后端；先完善现有刷新事件与组件反馈即可。"
        ]
      },
      "headline": "跨标签通知和服务端订阅是不同来源。",
      "featured": false
    },
    {
      "id": "PD07",
      "module": "服务配置与功能开关",
      "category": "状态与数据",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/_api/account.ts",
          "line": 139,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 151,
          "sha256": "496197cf94ebab53ce252365113992a951ba33529c910d9ae5220e51adc619ec"
        },
        {
          "path": "PwaSlotCash/src/app/_api/account.ts",
          "line": 166,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 168,
          "sha256": "496197cf94ebab53ce252365113992a951ba33529c910d9ae5220e51adc619ec"
        },
        {
          "path": "PwaSlotCash/src/app/_sdk/kyc/authenticatedBaseConfig.ts",
          "line": 22,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 32,
          "sha256": "750f20124aa4b1105c9fb87b377a0c1355e22ed0dc13c2831b146589c3b1f7cd"
        },
        {
          "path": "PwaSlotCash/src/app/_sdk/kyc/authenticatedBaseConfig.ts",
          "line": 38,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 54,
          "sha256": "750f20124aa4b1105c9fb87b377a0c1355e22ed0dc13c2831b146589c3b1f7cd"
        },
        {
          "path": "PwaSlotCash/src/app/MainSlice.ts",
          "line": 61,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 63,
          "sha256": "b98f066c5894f2645517bc6e940538f321372e465cd3c55ba032fd27b7ebca75"
        }
      ],
      "stake_ids": [
        "API-27",
        "API-28",
        "API-29"
      ],
      "current": "Pwa GameHomeData读取base_config，更新affiliate/PWA开关持久值并通知affiliate门禁；KYC ensureAuthenticatedBaseConfig按用户缓存当前会话成功response，复用inflight，旧身份响应不提交，loading/error时重置KYC开关。MainState有明确loadStatus。",
      "difference": "Stake独立feature flag REST+SSE、类型化evaluate失败回default；Pwa已有业务base_config和登录用户KYC配置生命周期，不能把已有开关误说成没有featureflags。未发现独立远程flag provider，但业务配置已经承担部分职责。",
      "suggestion": "沿用baseConfigLoadStatus，在入口组件明确pending/error/disabled三种情况；配置还没到不等于功能已关闭。 抽统一useFeatureAvailability(key)返回状态和原因；前端组件只消费，后台协议和base_config保持原样。优先将已有KYC的身份guard/inflight方式推广到同用户配置。",
      "limits": [
        "不推断featureflag后台管理能力。",
        "Stake跨市场Sports代码虽捕获，Stake.us编译开关为false；不应据源码存在把相关功能加入Pwa产品范围。"
      ],
      "raw": {
        "id": "PD07",
        "module": "服务配置与功能开关",
        "pwa_current": "Pwa GameHomeData读取base_config，更新affiliate/PWA开关持久值并通知affiliate门禁；KYC ensureAuthenticatedBaseConfig按用户缓存当前会话成功response，复用inflight，旧身份响应不提交，loading/error时重置KYC开关。MainState有明确loadStatus。",
        "pwa_evidence": [
          {
            "file": "PwaSlotCash/src/app/_api/account.ts",
            "line": 139,
            "end_line": 151,
            "snippet": "export async function GameHomeData(\n    params: Record<string, any> = {}\n): Promise<API_ACCOUNT.GameHomeDataResponse> {\n    params.userId = getUserId();\n    const config: HttpConfig = {\n        url: RequestMgr.getUrl(NetType.GameHomeData),\n        method: 'POST',\n        body: params\n        // headers: {\n        //     'Authorization': LoginNetManager.Instance.getLoginToken\n        // },\n    };\n    const response = await DoSendRequest<API_ACCOUNT.GameHomeDataResponse>(config);"
          },
          {
            "file": "PwaSlotCash/src/app/_api/account.ts",
            "line": 166,
            "end_line": 168,
            "snippet": "    notifyAffiliateConfigUpdated();\n    return response.data;\n}"
          },
          {
            "file": "PwaSlotCash/src/app/_sdk/kyc/authenticatedBaseConfig.ts",
            "line": 22,
            "end_line": 32,
            "snippet": "    if (loadedUserId === userId && loadedResponse && mainState.baseConfigLoadStatus === 'success') {\n        return loadedResponse;\n    }\n\n    if (inflightRequest && loadedUserId === userId) {\n        return inflightRequest;\n    }\n\n    loadedUserId = userId;\n    await UpdateMainState('KycBaseConfig/loading', {\n        baseConfigLoadStatus: 'loading',"
          },
          {
            "file": "PwaSlotCash/src/app/_sdk/kyc/authenticatedBaseConfig.ts",
            "line": 38,
            "end_line": 54,
            "snippet": "    const request = GameHomeData()\n        .then(async (response) => {\n            if (getUserId() !== userId || getLoginType() !== LoginType.normal) {\n                return null;\n            }\n\n            loadedResponse = response;\n            await UpdateMainState('KycBaseConfig/success', {\n                baseConfigLoadStatus: 'success',\n                xtg_kyc_switch: response.xtg_kyc_switch ?? 0,\n                kyc_scene: response.kyc_scene ?? '',\n                self_exclusion_kyc_scene_version: response.KYC_scene_version\n            });\n            return response;\n        })\n        .catch(async () => {\n            if (getUserId() === userId) {"
          },
          {
            "file": "PwaSlotCash/src/app/MainSlice.ts",
            "line": 61,
            "end_line": 63,
            "snippet": "    baseConfigLoadStatus: 'idle' | 'loading' | 'success' | 'error';\n    isKycAuthChecking: boolean;\n    xtg_kyc_switch?: number; // 开启 kyc"
          }
        ],
        "stake_evidence": [
          {
            "id": "API-27",
            "ledger": "work/stake-backend-notes.json",
            "claim": "app有POST /_api/feature-flag/v1/flags/state与evaluate/boolean/number/object/string，以及events；请求credentials include。启用inMemoryCache时bootstrap并订阅flags/stream SSE以withCredentials true。",
            "source": {
              "file": "outputs/stake-us-source/entry/app.Q5XUkswp.mjs",
              "character_offset_0based": 74282,
              "end_character_offset_0based_exclusive": 74872,
              "line": 2,
              "snippet": "Ke=async(e,t,n)=>{try{let t=await z(`/_api/feature-flag/v1/flags/state`,{body:JSON.stringify(e),method:`POST`,credentials:`include`,headers:q(`state`)},n);if(t.ok)return await t.json();throw await Y(t)}catch(e){return t?.error(`Failed to fetch feature flags: ${J(e)}`),null}},qe=async(e,t,n,r)=>{try{let n=await z(`/_api/feature-flag/v1/flags/${e}/evaluate/boolean`,{body:JSON.stringify(t),method:`POST`,credentials:`include`,headers:q(`boolean`)},r);if(n.ok)return{value:(await n.json()).value,reason:`remote`};throw await Y(n)}catch(r){return n?.error(`Failed to fetch boolean flag \"${e}\""
            }
          },
          {
            "id": "API-28",
            "ledger": "work/stake-backend-notes.json",
            "claim": "boolean evaluate失败catch返回value:请求defaultValue、reason:default、error文本；cached值若缺失或类型不符也回default。UI功能关闭可能是产品配置或旗标值，不能把代码存在当功能开启。",
            "source": {
              "file": "outputs/stake-us-source/entry/app.Q5XUkswp.mjs",
              "character_offset_0based": 74558,
              "end_character_offset_0based_exclusive": 75148,
              "line": 2,
              "snippet": "qe=async(e,t,n,r)=>{try{let n=await z(`/_api/feature-flag/v1/flags/${e}/evaluate/boolean`,{body:JSON.stringify(t),method:`POST`,credentials:`include`,headers:q(`boolean`)},r);if(n.ok)return{value:(await n.json()).value,reason:`remote`};throw await Y(n)}catch(r){return n?.error(`Failed to fetch boolean flag \"${e}\": ${J(r)}`),{value:t.defaultValue,reason:`default`,error:J(r)}}},Je=async(e,t,n,r)=>{try{let n=await z(`/_api/feature-flag/v1/flags/${e}/evaluate/number`,{body:JSON.stringify(t),method:`POST`,credentials:`include`,headers:q(`number`)},r);if(n.ok)return{value:(await n.json())."
            }
          },
          {
            "id": "API-29",
            "ledger": "work/stake-backend-notes.json",
            "claim": "app公开编译env有VITE_FEATURE_SPORTS_ENABLED:false；捕获JS仍包含Sports GraphQL操作和跨地区配置。160个操作不是Stake.us已开放160种功能，也不代表匿名能调用。",
            "source": {
              "file": "outputs/stake-us-source/entry/app.Q5XUkswp.mjs",
              "character_offset_0based": 72103,
              "end_character_offset_0based_exclusive": 72173,
              "line": 2,
              "snippet": "VITE_FEATURE_SPORTS_ENABLED:`false`,VITE_FEATURE_TIPS_ENABLED:`false`,"
            }
          }
        ],
        "comparison": "Stake独立feature flag REST+SSE、类型化evaluate失败回default；Pwa已有业务base_config和登录用户KYC配置生命周期，不能把已有开关误说成没有featureflags。未发现独立远程flag provider，但业务配置已经承担部分职责。",
        "frontend_changes": [
          "沿用baseConfigLoadStatus，在入口组件明确pending/error/disabled三种情况；配置还没到不等于功能已关闭。",
          "抽统一useFeatureAvailability(key)返回状态和原因；前端组件只消费，后台协议和base_config保持原样。优先将已有KYC的身份guard/inflight方式推广到同用户配置。"
        ],
        "unknowns": [
          "不推断featureflag后台管理能力。",
          "Stake跨市场Sports代码虽捕获，Stake.us编译开关为false；不应据源码存在把相关功能加入Pwa产品范围。"
        ]
      },
      "headline": "配置读取已有基础，可区分等待、失败和关闭。",
      "featured": true
    },
    {
      "id": "PD08",
      "module": "数据缓存与列表重绘",
      "category": "状态与数据",
      "sources": [
        {
          "path": "PwaSlotCash/src/Fwk/Http/comRequest.ts",
          "line": 87,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 96,
          "sha256": "3dd12015ddcb2adf6ea094596697e5f22fdd148ab062bd2af3bcae821152052c"
        },
        {
          "path": "PwaSlotCash/src/Fwk/Http/comRequest.ts",
          "line": 107,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 110,
          "sha256": "3dd12015ddcb2adf6ea094596697e5f22fdd148ab062bd2af3bcae821152052c"
        },
        {
          "path": "PwaSlotCash/src/Fwk/Http/comRequest.ts",
          "line": 154,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 157,
          "sha256": "3dd12015ddcb2adf6ea094596697e5f22fdd148ab062bd2af3bcae821152052c"
        },
        {
          "path": "PwaSlotCash/src/Fwk/Http/comRequest.ts",
          "line": 55,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 69,
          "sha256": "3dd12015ddcb2adf6ea094596697e5f22fdd148ab062bd2af3bcae821152052c"
        },
        {
          "path": "PwaSlotCash/src/app/stores/gameDataStore.ts",
          "line": 46,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 60,
          "sha256": "2929854ae4bbea520c7b0c1bf8d9cd1c92ad4d909610b56772406af7f1d864c6"
        },
        {
          "path": "PwaSlotCash/src/app/stores/gameDataStore.ts",
          "line": 177,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "end_line": 184,
          "sha256": "2929854ae4bbea520c7b0c1bf8d9cd1c92ad4d909610b56772406af7f1d864c6"
        }
      ],
      "stake_ids": [
        "API-13",
        "L09",
        "L11"
      ],
      "current": "Pwa comRequest按method/url/原body生成key；同key在途Promise复用，throttle窗口内复用lastResponse，结果按config.throttle设expireTime，5秒清理。gameDataStore持久化目录；convertMachineToGameEntry使用WeakMap稳定条目引用，配合memo(GameItem)避免无谓失效。",
      "difference": "Pwa的读取去重/短TTL比Stake捕获GraphQL Map更明确；Stake以cache.name+JSON.stringify(vars)键、无此类TTL。两边目录都需要数据适配后交卡片，Pwa已有引用稳定机制，应该保留，勿为模仿重写数据层。",
      "suggestion": "刷新大厅用旧缓存先画卡片再异步换新数据，新增loading/refreshing标记；避免重置列表导致闪白和滚动位置丢失。 把用户目录、通用目录和实时余额设不同freshness；保留1.5s用户请求去重和卡片WeakMap，不让持久目录缓存当实时账户值。",
      "limits": [
        "持久目录的失效/账号隔离政策未逐项确认；这里不判断cache泄露或正确性。",
        "请求节流也缓存失败返回的result，若希望组件“立即重试”需显式绕过/清理对应缓存，这属于前端交互约定。"
      ],
      "raw": {
        "id": "PD08",
        "module": "数据缓存与列表重绘",
        "pwa_current": "Pwa comRequest按method/url/原body生成key；同key在途Promise复用，throttle窗口内复用lastResponse，结果按config.throttle设expireTime，5秒清理。gameDataStore持久化目录；convertMachineToGameEntry使用WeakMap稳定条目引用，配合memo(GameItem)避免无谓失效。",
        "pwa_evidence": [
          {
            "file": "PwaSlotCash/src/Fwk/Http/comRequest.ts",
            "line": 87,
            "end_line": 96,
            "snippet": "function getThrottleKey(config: HttpConfig): string {\n    const method = config.method || 'GET';\n    let bodyStr = '';\n    try {\n        bodyStr = JSON.stringify(config.body ?? null);\n    } catch {\n        bodyStr = '[unstringifiable-body]';\n    }\n    return `${method}_${config.url}_${bodyStr}`;\n}"
          },
          {
            "file": "PwaSlotCash/src/Fwk/Http/comRequest.ts",
            "line": 107,
            "end_line": 110,
            "snippet": "        if (existing?.inflight) {\n            return existing.inflight as Promise<HttpResponse<T>>;\n        }\n"
          },
          {
            "file": "PwaSlotCash/src/Fwk/Http/comRequest.ts",
            "line": 154,
            "end_line": 157,
            "snippet": "            entry.expireTime = Date.now() + config.throttle;\n            startCleanupLoop();\n        }\n"
          },
          {
            "file": "PwaSlotCash/src/Fwk/Http/comRequest.ts",
            "line": 55,
            "end_line": 69,
            "snippet": "    // 每 5 秒检查一次是否有过期数据需要清理\n    cleanupTimer = setInterval(() => {\n        const now = Date.now();\n        if (throttleMap.size === 0) {\n            stopCleanupLoop();\n            return;\n        }\n\n        for (const [key, entry] of throttleMap.entries()) {\n            // 如果设置了过期时间，且已过期\n            if (entry.expireTime && now > entry.expireTime) {\n                // 释放大对象内存\n                entry.lastResponse = undefined;\n                entry.expireTime = undefined;\n"
          },
          {
            "file": "PwaSlotCash/src/app/stores/gameDataStore.ts",
            "line": 46,
            "end_line": 60,
            "snippet": "// 使用 WeakMap 记忆化：同一个 machine 引用返回同一个 GameData 引用，\n// 让上层 map/sort 重算时元素引用保持稳定，memo(GameItem) 的 data prop 不再无谓失效。\nconst machineEntryCache = new WeakMap<object, GameData>();\nexport const convertMachineToGameEntry = (machine: API_ACCOUNT.MachineInfo): GameData => {\n    // 直接返回 machine,保持 skinId 的原始类型(string | number)\n    const cached = machineEntryCache.get(machine as object);\n    if (cached) return cached;\n    const entry = {\n        ...machine\n    } as unknown as GameData;\n    machineEntryCache.set(machine as object, entry);\n    return entry;\n};\n\n// 将 GameEntries 转换为 MachineInfo 的辅助函数（用于收藏功能）"
          },
          {
            "file": "PwaSlotCash/src/app/stores/gameDataStore.ts",
            "line": 177,
            "end_line": 184,
            "snippet": "            name: 'game-data-storage',\n            partialize: (state) => ({\n                gameList: state.gameList,\n                partitions: state.partitions,\n                allGameConfig: state.allGameConfig\n            }) // 使用默认存储（不加密）\n        }\n    )"
          }
        ],
        "stake_evidence": [
          {
            "id": "API-13",
            "ledger": "work/stake-backend-notes.json",
            "claim": "仅cache参数存在才生成cache.name+JSON.stringify(variables)键；命中返回data。只有无GraphQL error的响应写入Map。本类没有自动TTL、LRU或operationName加入键；提供clear/remove手动清理。",
            "source": {
              "file": "outputs/stake-us-source/chunks/Cu3B9wdT.mjs",
              "character_offset_0based": 25955,
              "end_character_offset_0based_exclusive": 26315,
              "line": 30,
              "snippet": "let u=r?r.name+JSON.stringify(n):null;if(u&&this.cacheMap.has(u))return{data:this.cacheMap.get(u),errors:void 0,context:c};let p=await l.fetch(_.url,{..._});if(p.ok){let e=await p.json(),r=e?.errors||[],a=e?.data;if(r.length!==0){{let e={errorType:`api load error`,status:p.status,url:this.url,DOC_NAME:d,errorCount:r.length,errors:r.map(e=>({message:e.message"
            }
          },
          {
            "id": "L09",
            "ledger": "work/stake-layout-deep.json",
            "claim": "首页由11个数据源并发拼接",
            "source": {
              "path": "outputs/stake-us-source/nodes/55.BHHpgWjN.mjs",
              "sha256": "4f9f8c771b2819d05c92cfafa92edc7062bdff5cca81877c633445cb9759162f",
              "line": 2,
              "character_start": 16887,
              "character_end": 18237,
              "byte_start": 16887,
              "snippet": "jt=async e=>{let{url:t}=e,n=Ge[`/`](`https://${t.hostname}`);try{let[t,r,i,a,o,s,c,l,u,d,f]=await ve([Tt(e),Et(e),Dt(e),kt(e),ee?At(e):Promise.resolve(void 0),We(e,bt),L===`sweeps`?St(e):Promise.resolve(void 0),wt(e),se(e,{slug:bt}),ce(e,{start:0,end:24}),Ot(e)]),p=u?.homepage?Ye({casinoHeroes:u?.casinoHeroes??[],fallbackCasinoHeroes:u?.fallbackCasinoHeroes??[],sportsHeroes:u?.sportsHeroes??[],fallbackSportsHeroes:u?.fallbackSportsHeroes??[]}):[],m=[...d?.promotions??[]].filter(e=>p.some(t=>Xe(t,e))),h=s??n;if(u?.homepage){let{seoTitle:e,seoDescription:t,jsonLd:n,thumbnail:r,hreflangList:i,thumbnailAlt:a,robots:o,canonical:s}=u.homepage;h=Ue({title:e,description:t,jsonLd:n,thumbnail:r,thumbnailAlt:a,hreflangList:i,robots:o,canonical:s})}return{heroes:p,promotions:m,vipProgress:t,userRecentGameList:r,trendingGamesList:i,playerCountByScope:a?.playerCountByScope,trendingSportsList:o?.data,kuratorCollection:c,pokerData:l,seo:h,personalisedRecommendationGames:f}}catch{return{vipProgress:null,playerCountByScope:null,userRecentGamesList:null,trendingGamesList:null,trendingSportsList:null,heroes:[],promotions:[],seo:n,personalisedRecommendationGames:null}}},Mt=new Set([`$$slots`,`$$events`,`$$legacy`]);function Nt(e,n){j(n,!0),w(n,Mt);let r=g(()=>({casino:Number(he.data.playerCountByScope?.casinoPlayerCount??0),sportsbook:Number(he.data"
            }
          },
          {
            "id": "L11",
            "ledger": "work/stake-layout-deep.json",
            "claim": "首页将异构数据适配成统一group/cards结构",
            "source": {
              "path": "outputs/stake-us-source/nodes/55.BHHpgWjN.mjs",
              "sha256": "4f9f8c771b2819d05c92cfafa92edc7062bdff5cca81877c633445cb9759162f",
              "line": 2,
              "character_start": 44228,
              "character_end": 45958,
              "byte_start": 44228,
              "snippet": "I=g(()=>({group:{slug:`recent-games`,to:`/casino/recent`,icon:`continue`,translation:i()._(ze.recentGames),gameCount:0},cards:O(x)?.map(e=>e)||[]})),ee=g(()=>({group:{slug:`trending-games`,to:`/casino/group/recommended-slots?sort=popular`,icon:`trending`,translation:i()._(ze.trendingGames),gameCount:0},cards:O(S)?.map(e=>e?.game)||[]})),R=g(()=>O(F)?{list:{group:{slug:`recommendation-engine`,to:`/casino/group/games-for-you`,icon:`popular`,translation:i()._(ze.recommendedGames),gameCount:O(F).games.length},cards:O(F).games},modelVersion:O(F).modelVersion,sequenceId:O(F).sequenceId}:null),z=g(()=>({group:{slug:`trending-sports`,to:`/sports/home`,icon:`sports-basketball`,translation:i()._(ze.trendingSports),gameCount:0},cards:O(w)?.topSportList?.map(e=>({...mt(L,e,o()),type:e.sportType}))||[]})),B=g(()=>O(N)?.find(e=>e.group.slug===`stake-originals`)),V=g(()=>({group:{slug:O(B)?.group?.slug??`stake-originals`,to:`/casino/group/stake-originals?sort=popular`,icon:O(B)?.group?.icon??``,translation:O(B)?.group?.translation??``,gameCount:O(B)?.group?.gameCount??0},cards:O(B)?.group?.groupGamesList?.map(e=>e?.game)||[]})),H=n(void 0),ne=n(!1);v(async()=>{h(ne,await An(),!0)}),v(()=>{try{let e=c().url.searchParams.get(`confirm`),t=c().url.searchParams.get(`key`);e===`enableTfa`&&t&&(async()=>{let e=Be({page:c(),paramsToRemove:[`confirm`,`key`]});await P(e,{replaceState:!0}),h(H,setTimeout(()=>{oe(t)},450),!0)})()}catch{}return()=>{clearTimeout(O(H))}}),O(b)&&at.set(O(b)),a(()=>{O(b)&&at.set(O(b))});let U=g(ue);var ie=jn(),ae=d(ie),G=e=>{Ke(e,{get seo(){return O(M)}})};s(ae,e=>{O(M)&&e(G)});var K=f(ae,2),se=C(K);m(se,()=>xe,(e,n)=>{n(e,{padding:!1,get initialWidth(){return y},children:(e,n)=>{var r=t(),i=d(r),a="
            }
          }
        ],
        "comparison": "Pwa的读取去重/短TTL比Stake捕获GraphQL Map更明确；Stake以cache.name+JSON.stringify(vars)键、无此类TTL。两边目录都需要数据适配后交卡片，Pwa已有引用稳定机制，应该保留，勿为模仿重写数据层。",
        "frontend_changes": [
          "刷新大厅用旧缓存先画卡片再异步换新数据，新增loading/refreshing标记；避免重置列表导致闪白和滚动位置丢失。",
          "把用户目录、通用目录和实时余额设不同freshness；保留1.5s用户请求去重和卡片WeakMap，不让持久目录缓存当实时账户值。"
        ],
        "unknowns": [
          "持久目录的失效/账号隔离政策未逐项确认；这里不判断cache泄露或正确性。",
          "请求节流也缓存失败返回的result，若希望组件“立即重试”需显式绕过/清理对应缓存，这属于前端交互约定。"
        ]
      },
      "headline": "去重、缓存和引用稳定已有实现。",
      "featured": false
    }
  ],
  "scope_note": "计算器与Stake公共查询不参与两站对比或评分；原始记录留在技术资料中。"
}