From 85aa0249b96cd73cc26cc6bf24441419990318dd Mon Sep 17 00:00:00 2001 From: seaislee1209 Date: Sat, 25 Apr 2026 14:17:01 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20v0.19.5=20=E7=94=9F=E6=88=90?= =?UTF-8?q?=E9=A1=B5=E6=85=A2=E9=80=9F=E5=BE=80=E4=B8=8A=E6=BB=9A=E4=BB=8D?= =?UTF-8?q?=E8=B7=B3=E5=88=B0=E5=BA=95=E9=83=A8=20=E2=80=94=20=E5=8F=8C?= =?UTF-8?q?=E5=B1=82=20anchor=20=E5=8F=A0=E5=8A=A0=E6=A0=B9=E5=9B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v0.19.4 只修了 useEffect 的 scrollToBottom 误触, 没修 handleScroll 里的 anchor 累加。用户实测: 鼠标滚轮慢速 / 慢拖滑动条往上翻仍会 跳到最底部, 但快速拽到顶不复现。 根因(双层 anchor 叠加) 1. 浏览器自动 scroll anchoring(默认 overflow-anchor: auto): 滚动 容器头部插入内容时浏览器自动 scrollTop += diff, 保持视觉位置 2. handleScroll 里 .then 又手动 el.scrollTop += diff => 双倍 anchor, 总位移 = 2 * diff, 把 scrollTop 推到 max(底部) 为什么"快速拽不复现, 慢速复现": 浏览器 scroll anchoring 在用户主动 scroll 期间会暂时关闭。快速 拽到顶后立即 loadMore 完成, 浏览器把"用户刚释放"视作仍在 scroll 跳过自动 anchor, 只手动一次, 不叠加。慢速操作时 scroll event 间 有 100~300ms 静止间隔, 浏览器认为 scroll 已停, 自动 anchor 启动 + 我们手动 anchor = 双倍。 修复(两道防线) 1. CSS: .contentArea 加 overflow-anchor: none, 彻底关掉浏览器自动 anchor, 由代码统一管 — 这是根因修复 2. handleScroll: 加 loadMoreInFlightRef 防重入 flag, 慢速操作下 多次进入 if 分支只 schedule 一次 anchor; rAF 完成后清 flag — 兜底防御, 避免极端时序下 rAF 累加 Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/components/VideoGenerationPage.module.css | 4 ++++ web/src/components/VideoGenerationPage.tsx | 14 +++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/web/src/components/VideoGenerationPage.module.css b/web/src/components/VideoGenerationPage.module.css index 407a6cb..d72f20f 100644 --- a/web/src/components/VideoGenerationPage.module.css +++ b/web/src/components/VideoGenerationPage.module.css @@ -17,6 +17,10 @@ flex: 1; overflow-y: auto; overflow-x: hidden; + /* 关掉浏览器自动 scroll anchoring:往上加载历史时由 handleScroll 里的 + anchor 逻辑统一管,避免浏览器默认的 anchor 与我们手动 +diff 叠加, + 导致慢速滚动 / 慢拖滑动条时页面被推到最底部。 */ + overflow-anchor: none; } .emptyArea { diff --git a/web/src/components/VideoGenerationPage.tsx b/web/src/components/VideoGenerationPage.tsx index 26f6b24..1fd0cd3 100644 --- a/web/src/components/VideoGenerationPage.tsx +++ b/web/src/components/VideoGenerationPage.tsx @@ -22,6 +22,9 @@ export function VideoGenerationPage() { const scrollRef = useRef(null); const prevLastIdRef = useRef(null); const initialLoadRef = useRef(true); + // 防重入 flag:loadMore + anchor 期间,handleScroll 多次触发不再 schedule 多个 rAF, + // 避免 anchor 累加把页面推到底(慢速滚轮 / 慢拖滑动条场景)。 + const loadMoreInFlightRef = useRef(false); const savedScrollTop = useGenerationStore((s) => s.savedScrollTop); const saveScrollPosition = useGenerationStore((s) => s.saveScrollPosition); const [detailTaskId, setDetailTaskId] = useState(null); @@ -76,15 +79,20 @@ export function VideoGenerationPage() { const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight; setShowScrollBottom(distanceFromBottom > 300); - // Trigger loadMore when scrolled within 100px of the top - if (scrollRef.current.scrollTop < 100) { + // Trigger loadMore when scrolled within 100px of the top. + // ref flag 守卫:只有第一次进入分支时才 schedule loadMore + anchor; + // 后续 handleScroll(慢速操作下持续触发)直接跳过,避免多次 rAF 排队累加 +diff。 + if (scrollRef.current.scrollTop < 100 && !loadMoreInFlightRef.current) { + loadMoreInFlightRef.current = true; const el = scrollRef.current; const prevHeight = el.scrollHeight; loadMore().then(() => { - // After older tasks are prepended, restore visual position so user doesn't jump + // After older tasks are prepended, restore visual position so user doesn't jump. + // CSS overflow-anchor: none 已禁用浏览器自动 anchor,由这里独立完成。 requestAnimationFrame(() => { const diff = el.scrollHeight - prevHeight; if (diff > 0) el.scrollTop += diff; + loadMoreInFlightRef.current = false; }); }); } From 3f858257ea9c11567fabb048ad9c5fa85543338e Mon Sep 17 00:00:00 2001 From: seaislee1209 Date: Mon, 27 Apr 2026 17:40:42 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20v0.19.6=20CI=20deploy.yaml=20retry?= =?UTF-8?q?=20=E5=BE=AA=E7=8E=AF=E5=A4=B1=E8=B4=A5=E6=97=B6=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=20exit=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因 deploy.yaml 6 处 retry 循环用 \`for ... do command && break; done\` 模式, bash for 循环本身的 exit code 永远是 0(只要循环正常结束), 即使所有 attempt 都失败。CI 看 step exit 0 -> 误判绿色。 实际事故 v0.19.5 (85aa024) push dev 后 Gitea Actions 显示绿色钩, 但测试服 K8s 上没有创建对应的 ReplicaSet, web pod 仍跑 v0.19.4。 查 K8s ReplicaSet 历史发现自 4-24 12:12 之后没有任何新 RS, 说明 deploy step 的 kubectl apply 没把新 image tag 提交到 etcd (或某处中间静默失败被吞)。SWR 上镜像已经推上去, 是 deploy 这步 后续操作出了问题但 CI 没察觉。 修复 6 处 retry 全部加 \`ok=0/ok=1/break\` flag, 循环结束后 \`[ $ok -eq 1 ] || exit 1\` 守卫, 真失败时 step exit 非 0 -> CI 红色: - backend build (3 次) - backend push (3 次) - web build (3 次) - web push (3 次) - kubectl download (3 次) - deploy to K3s (5 次, 含 kubectl apply / rollout restart) 以后再遇到部署失败, Gitea Actions 会真正显示红色, 不再"假绿色" 骗人。同时已有的 Report-failure-to-Log-Center step (if: failure()) 会被触发, 飞书 / log-center 收到告警。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/deploy.yaml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index f408c57..701fcb5 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -49,45 +49,55 @@ jobs: id: build_backend run: | set -o pipefail + ok=0 for attempt in 1 2 3; do echo "Build backend attempt $attempt/3..." DOCKER_BUILDKIT=0 docker build \ --tag ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/video-backend:${{ env.IMAGE_TAG }} \ --tag ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/video-backend:latest \ - ./backend 2>&1 | tee /tmp/build.log && break + ./backend 2>&1 | tee /tmp/build.log && { ok=1; break; } echo "Attempt $attempt failed, retrying in 10s..." && sleep 10 done + [ $ok -eq 1 ] || { echo "ERROR: backend build failed after 3 attempts"; exit 1; } + ok=0 for attempt in 1 2 3; do docker push ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/video-backend:${{ env.IMAGE_TAG }} && \ - docker push ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/video-backend:latest && break + docker push ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/video-backend:latest && { ok=1; break; } echo "Push attempt $attempt failed, retrying in 10s..." && sleep 10 done + [ $ok -eq 1 ] || { echo "ERROR: backend push failed after 3 attempts"; exit 1; } - name: Build and Push Web id: build_web run: | set -o pipefail + ok=0 for attempt in 1 2 3; do echo "Build web attempt $attempt/3..." DOCKER_BUILDKIT=0 docker build \ --tag ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/video-web:${{ env.IMAGE_TAG }} \ --tag ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/video-web:latest \ - ./web 2>&1 | tee -a /tmp/build.log && break + ./web 2>&1 | tee -a /tmp/build.log && { ok=1; break; } echo "Attempt $attempt failed, retrying in 10s..." && sleep 10 done + [ $ok -eq 1 ] || { echo "ERROR: web build failed after 3 attempts"; exit 1; } + ok=0 for attempt in 1 2 3; do docker push ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/video-web:${{ env.IMAGE_TAG }} && \ - docker push ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/video-web:latest && break + docker push ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/video-web:latest && { ok=1; break; } echo "Push attempt $attempt failed, retrying in 10s..." && sleep 10 done + [ $ok -eq 1 ] || { echo "ERROR: web push failed after 3 attempts"; exit 1; } - name: Setup Kubectl run: | if ! command -v kubectl &>/dev/null; then + ok=0 for attempt in 1 2 3; do - curl -LO "https://files.m.daocloud.io/dl.k8s.io/release/v1.28.0/bin/linux/amd64/kubectl" && break + curl -LO "https://files.m.daocloud.io/dl.k8s.io/release/v1.28.0/bin/linux/amd64/kubectl" && { ok=1; break; } echo "Download attempt $attempt failed, retrying in 5s..." && sleep 5 done + [ $ok -eq 1 ] || { echo "ERROR: kubectl download failed after 3 attempts"; exit 1; } chmod +x kubectl && mv kubectl /usr/bin/kubectl fi kubectl version --client @@ -135,6 +145,7 @@ jobs: # All kubectl operations with retry (K3s 内网连接可能抖动) export KUBECTL_TIMEOUT="--request-timeout=4s" + ok=0 for attempt in 1 2 3 4 5; do echo "Deploy attempt $attempt/5..." { @@ -169,10 +180,11 @@ jobs: kubectl $KUBECTL_TIMEOUT rollout restart deployment/video-backend kubectl $KUBECTL_TIMEOUT rollout restart deployment/celery-worker kubectl $KUBECTL_TIMEOUT rollout restart deployment/video-web - } 2>&1 | tee /tmp/deploy.log && break + } 2>&1 | tee /tmp/deploy.log && { ok=1; break; } echo "Attempt $attempt failed, retrying in 30s..." sleep 30 done + [ $ok -eq 1 ] || { echo "ERROR: deploy to K3s failed after 5 attempts — check /tmp/deploy.log"; exit 1; } # ===== Log Center: failure reporting ===== - name: Report failure to Log Center