{
  "project": "PwaSlotCash",
  "revision": "326ea0a335dbfa05d26845ce603d4bf9fb62e5ac",
  "scope": "静态源码核对；未修改项目；未运行产品",
  "items": [
    {
      "id": "PWA-01",
      "priority": "先处理",
      "kind": "正确性保护",
      "title": "首页旧请求失效：换账号或离开后不再回写",
      "fact": "首页 effect 随 userId 重跑，但清理只清除当时已有的 Live 计时器。异步返回后仍能写分区、收藏、配置，并新建计时器。",
      "impact": "切账号或离开页面时，旧任务仍可能更新当前状态，或在清理之后再安排 Live 任务。代码路径明确；本轮没有运行复现。",
      "change": "给每次 effect 一个失效标记或请求代次；每个 await 返回后、共享状态写入前、建立和执行 Live 计时器前都检查它。下层 GameHomeData 也会写存储，因此只挡页面 setState 不能保护所有副作用；账号相关写入应一起检查请求所属身份。",
      "checks": [
        "A账号请求延迟返回，切到B后只接受B结果。",
        "请求返回前离开首页，旧任务不建立新 Live 计时器。",
        "Live 已开始后切账号，下层回写也按身份／代次过滤。"
      ],
      "scope": "保留现有并发加载；不必新建全站状态框架。",
      "confidence": "代码确认存在保护缺口；影响待运行验证",
      "refs": [
        [
          "src/app/(pages)/home/page.tsx",
          198,
          241
        ],
        [
          "src/app/(pages)/home/page.tsx",
          314,
          318
        ],
        [
          "src/app/_api/account.ts",
          139,
          171
        ],
        [
          "src/app/_sdk/kyc/authenticatedBaseConfig.ts",
          38,
          51
        ]
      ],
      "pseudo": "开始任务：保存当前账号与任务代次\n清理：标记失效，清计时器\n每次异步返回：若任务失效或账号变了，退出\n确认仍有效：再更新状态、存储或安排后续任务",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/(pages)/home/page.tsx",
          "line": 198,
          "end_line": 241,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "86da94e371d2cf3943aa23cb5f4eb71d553305aab09c1f8483ae9abe3763773c"
        },
        {
          "path": "PwaSlotCash/src/app/(pages)/home/page.tsx",
          "line": 314,
          "end_line": 318,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "86da94e371d2cf3943aa23cb5f4eb71d553305aab09c1f8483ae9abe3763773c"
        },
        {
          "path": "PwaSlotCash/src/app/_api/account.ts",
          "line": 139,
          "end_line": 171,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "496197cf94ebab53ce252365113992a951ba33529c910d9ae5220e51adc619ec"
        },
        {
          "path": "PwaSlotCash/src/app/_sdk/kyc/authenticatedBaseConfig.ts",
          "line": 38,
          "end_line": 51,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "750f20124aa4b1105c9fb87b377a0c1355e22ed0dc13c2831b146589c3b1f7cd"
        }
      ]
    },
    {
      "id": "PWA-02",
      "priority": "先处理",
      "kind": "请求与加载",
      "title": "首页 Base Config 只选一条获取路径",
      "fact": "首页先在 Promise.all 中调用 GameHomeData；成功后，又在内部同名 oldResponse 变量中按登录类型调用 ensureAuthenticatedBaseConfig 或 GameHomeData。首个响应主要成为成功门槛，没有被用于后续解构。",
      "impact": "访客路径有连续两次请求调用；正式用户路径可能多取一次同类配置。第二次还会延后配置发布。底层去重有条件：GameHomeData 本处没有 throttle，comRequest 仅在 throttle>0 时走共享机制；不声称已测得线上请求次数或耗时。",
      "change": "在并发数组里按登录类型选择统一获取函数，后面直接使用其结果；正式用户继续走 KYC 的身份保护和缓存，不用裸请求替代。取消“第一次失败就不尝试第二条路径”的隐式门槛，失败恢复明确处理。",
      "checks": [
        "访客首页成功加载只发出一次该配置请求（排除独立调用者）。",
        "正式用户复用同账号在途请求，KYC状态仍正确。",
        "失败时显示既有兜底，切账号旧配置不发布。"
      ],
      "scope": "只整理首页调用链；不改 REST／GraphQL 架构。",
      "confidence": "重复获取路径已确认；性能收益需网络测量",
      "refs": [
        [
          "src/app/(pages)/home/page.tsx",
          205,
          214
        ],
        [
          "src/app/(pages)/home/page.tsx",
          236,
          243
        ],
        [
          "src/app/_api/account.ts",
          139,
          151
        ],
        [
          "src/Fwk/Http/comRequest.ts",
          99,
          110
        ]
      ],
      "pseudo": "配置请求 = 正式用户 ? 获取带身份保护的配置 : 获取访客配置\n[推荐, 配置] = 并行等待两个请求\n检查任务是否仍有效\n使用本次配置结果，避免成功后重新请求",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/(pages)/home/page.tsx",
          "line": 205,
          "end_line": 214,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "86da94e371d2cf3943aa23cb5f4eb71d553305aab09c1f8483ae9abe3763773c"
        },
        {
          "path": "PwaSlotCash/src/app/(pages)/home/page.tsx",
          "line": 236,
          "end_line": 243,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "86da94e371d2cf3943aa23cb5f4eb71d553305aab09c1f8483ae9abe3763773c"
        },
        {
          "path": "PwaSlotCash/src/app/_api/account.ts",
          "line": 139,
          "end_line": 151,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "496197cf94ebab53ce252365113992a951ba33529c910d9ae5220e51adc619ec"
        },
        {
          "path": "PwaSlotCash/src/Fwk/Http/comRequest.ts",
          "line": 99,
          "end_line": 110,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "3dd12015ddcb2adf6ea094596697e5f22fdd148ab062bd2af3bcae821152052c"
        }
      ]
    },
    {
      "id": "PWA-03",
      "priority": "先处理",
      "kind": "消息稳健性",
      "title": "游戏消息先检查类型，再调用业务回调",
      "fact": "useGameMessage 已校验 origin，也在卸载时移除监听；随后直接读取 data.type 并调用 startsWith，没有先确认 data 是对象、type 是字符串。两个游戏容器共用这个 Hook。",
      "impact": "来自允许 origin 的 null 消息会在读取 type 时出错；type 为非零数字等非字符串时可能在 startsWith 出错。这是可从表达式确定的边界，不等于已发现可利用攻击。",
      "change": "先检查 data 非空对象、type 为字符串、消息类型在约定范围内；传入当前 iframe 的引用，结合 origin 校验 event.source，必要时按游戏／会话标识隔离旧消息。source／会话保护是增强建议，消息格式防护是已确认缺口。",
      "checks": [
        "null、字符串、数字type等消息被忽略且无异常。",
        "合法 game_* 消息继续触发原有处理。",
        "允许 origin 的其他窗口，以及旧游戏消息不误触当前实例。"
      ],
      "scope": "保留现有监听清理；不宣称全项目缺少退出清理。",
      "confidence": "类型保护缺口已确认；来源增强需兼容协议验证",
      "refs": [
        [
          "src/app/hooks/useGameMessage.tsx",
          19,
          39
        ],
        [
          "src/app/(game)/components/GameFrame/GameFrame.tsx",
          203,
          249
        ],
        [
          "src/app/(game)/components/BridgeFrame/BridgeFrame.tsx",
          28,
          51
        ]
      ],
      "pseudo": "若 origin 不匹配：忽略\n若 source 不是当前游戏窗口：忽略\n若 data 不是对象或 type 不是字符串：忽略\n若类型不在协议约定中：忽略\n否则进入业务回调",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/hooks/useGameMessage.tsx",
          "line": 19,
          "end_line": 39,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "05aaeba2ee98bd71100cf969f65b4fcafe139607f84e2814193ae7335cebc207"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/components/GameFrame/GameFrame.tsx",
          "line": 203,
          "end_line": 249,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "7964d7ef54d9982a76cc898d02f178c6455ecb18107a2004f3e385125e44601e"
        },
        {
          "path": "PwaSlotCash/src/app/(game)/components/BridgeFrame/BridgeFrame.tsx",
          "line": 28,
          "end_line": 51,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "a5443b7230fa2c80a16abc17b3570be49dbb4cba0665c6f45d8b42888367225f"
        }
      ]
    },
    {
      "id": "PWA-04",
      "priority": "小改即可",
      "kind": "渲染订阅",
      "title": "余额组件只订阅实际显示的字段",
      "fact": "CoinLabel 通过 selectUserInfo 订阅整个用户对象；SC换算 useMemo 的依赖也是整个 userInfo。组件实际主要展示 rewardType、convertTotalScAmount、gcAmount。",
      "impact": "其他用户字段更新也可能触发此组件渲染和重新换算。能确认订阅粒度较大，不能据此量化卡顿或帧率损失。",
      "change": "按 rewardType、SC总额、GC余额分别取值，或使用稳定的细粒度 selector。仍用现有 ToNormalUnit 单位换算；显示总SC与可兑换SC的区别不能混淆。",
      "checks": [
        "只改昵称等无关字段，不触发余额值重新计算；用Profiler确认渲染变化。",
        "GC／SC切换、余额刷新和滚动数字仍正常。",
        "SC单位和总额／可兑换额与现有合同一致。"
      ],
      "scope": "不引入 Stake 事件账本，也不重写 Redux。",
      "confidence": "订阅粒度优化明确；实际收益待测",
      "refs": [
        [
          "src/app/_components/Coin/CoinLabel.tsx",
          29,
          41
        ],
        [
          "src/app/_components/Coin/CoinLabel.tsx",
          61,
          74
        ],
        [
          "src/app/userSlice.ts",
          186,
          192
        ]
      ],
      "pseudo": "币种 = 订阅 rewardType\nSC原值 = 订阅 convertTotalScAmount\nGC原值 = 订阅 gcAmount\nSC显示值 = 按现有单位规则换算SC原值",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/_components/Coin/CoinLabel.tsx",
          "line": 29,
          "end_line": 41,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "acbd7b2d4a69cae33dfd91a686af573be3ff16e2a2fa33bdc043ee15c6f64412"
        },
        {
          "path": "PwaSlotCash/src/app/_components/Coin/CoinLabel.tsx",
          "line": 61,
          "end_line": 74,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "acbd7b2d4a69cae33dfd91a686af573be3ff16e2a2fa33bdc043ee15c6f64412"
        },
        {
          "path": "PwaSlotCash/src/app/userSlice.ts",
          "line": 186,
          "end_line": 192,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "549e6a68a42b41685fe58179198c42ea593535c0e91674f14a82e773435445a2"
        }
      ]
    },
    {
      "id": "PWA-05",
      "priority": "顺手整理",
      "kind": "表单维护",
      "title": "同步表单校验直接返回结果",
      "fact": "登录 validateForm 内全部是同步判断，却构造 Promise 并多次 reject／resolve；密码失焦也重复调用 validatePassword。输入、失焦和提交分别维护错误。",
      "impact": "当前 Promise 在首次 reject 后不会变成成功，这里不指控校验绕过。可优化的是重复判断和控制流，让错误集合与返回值更容易理解和复用。",
      "change": "一次计算字段错误并返回布尔值／错误集合；密码校验结果只算一次。先在登录页整理，再决定是否复用到注册。保持现有 GEO、resetForm 和业务提交顺序。",
      "checks": [
        "手机／邮箱、密码／验证码各分支仍使用原规则。",
        "失败字段可定位，修正后按既有时机清错。",
        "埋点次数与GEO检查顺序不因重构改变。"
      ],
      "scope": "轻量整理；不为此引入新的表单框架。",
      "confidence": "同步Promise和重复校验已确认；属于维护优化",
      "refs": [
        [
          "src/app/(auth)/login/modalPage.tsx",
          229,
          271
        ],
        [
          "src/app/(auth)/login/modalPage.tsx",
          275,
          287
        ]
      ],
      "pseudo": "错误集合 = 校验当前登录方式的必要字段\n一次发布错误集合\n有错误：结束提交\n无错误：按原业务顺序继续",
      "sources": [
        {
          "path": "PwaSlotCash/src/app/(auth)/login/modalPage.tsx",
          "line": 229,
          "end_line": 271,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "9cc2898ec8147de7e106e37339c819fc7edb14a1611dff31b87238fb804d2b5c"
        },
        {
          "path": "PwaSlotCash/src/app/(auth)/login/modalPage.tsx",
          "line": 275,
          "end_line": 287,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "9cc2898ec8147de7e106e37339c819fc7edb14a1611dff31b87238fb804d2b5c"
        }
      ]
    },
    {
      "id": "PWA-06",
      "priority": "先核验再收口",
      "kind": "日志脱敏",
      "title": "请求调试日志默认遮蔽认证与个人字段",
      "fact": "AuthInterceptor 在 Util.isLog 为真时，记录加密前的整份配置副本；在这之前，isToken 条件下会把当前 token 写入 Authorization。",
      "impact": "配置副本可能包含认证头和请求正文。这里只确认受日志开关控制的调用点，不声称当前生产环境已启用日志或已发生泄露。",
      "change": "统一日志输出投影：保留 URL、方法、状态和耗时，遮蔽认证头、密码、验证码及个人字段。核验 LogUtil 的后续处理和生产开关；若它已有完整脱敏，应补调用链证据而非重复实现。",
      "checks": [
        "日志开／关两种状态都检查。",
        "用虚构token／密码／验证码验证日志中无原值。",
        "错误定位仍保留必要接口名和状态，不采集真实凭据。"
      ],
      "scope": "改观测输出；不涉及后端授权或密钥轮换。",
      "confidence": "全配置日志调用已确认；最终输出与生产状态待核验",
      "refs": [
        [
          "src/Fwk/Http/Interceptors/AuthInterceptor.ts",
          14,
          33
        ]
      ],
      "pseudo": "调试日志 = {接口名, 方法, 状态, 耗时}\n认证与个人字段 = 遮蔽或直接省略\n输出允许字段，而非完整请求配置",
      "sources": [
        {
          "path": "PwaSlotCash/src/Fwk/Http/Interceptors/AuthInterceptor.ts",
          "line": 14,
          "end_line": 33,
          "snippet": "[项目代码保留在本地；线上版仅提供定位]",
          "sha256": "4c1299586a7c9da84cf6863630662d48cf1e6740ac8a57d370b2eb8256a39c0d"
        }
      ]
    }
  ]
}