Compare commits

...

3 Commits

Author SHA1 Message Date
seaislee1209
7267c0bce5 Merge dev: v0.19.5~v0.19.6 (生成页慢速滚跳底根因修复 / CI retry 假绿色根因修复)
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m48s
- v0.19.5 (85aa024): 生成页慢速往上滚仍跳到底部 — 双层 anchor 叠加根因
  CSS .contentArea 加 overflow-anchor: none 关掉浏览器自动 anchor +
  handleScroll 加 loadMoreInFlightRef 防 rAF 累加
- v0.19.6 (3f85825): CI deploy.yaml 6 处 retry 循环失败时正确 exit 1
  根除 "for ... && break; done" 模式吞错误导致的 "假绿色钩" 误判
  (v0.19.5 因此卡了 2 天没自动部署到测试服, 手动 patch 才上线)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:59:12 +08:00
seaislee1209
3f858257ea fix: v0.19.6 CI deploy.yaml retry 循环失败时正确 exit 1
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m4s
根因 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) <noreply@anthropic.com>
2026-04-27 17:40:42 +08:00
seaislee1209
85aa0249b9 fix: v0.19.5 生成页慢速往上滚仍跳到底部 — 双层 anchor 叠加根因
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 14m10s
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) <noreply@anthropic.com>
2026-04-25 14:17:01 +08:00
3 changed files with 33 additions and 9 deletions

View File

@ -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

View File

@ -17,6 +17,10 @@
flex: 1;
overflow-y: auto;
overflow-x: hidden;
/* 关掉浏览器自动 scroll anchoring往上加载历史时由 handleScroll 里的
anchor 逻辑统一管避免浏览器默认的 anchor 与我们手动 +diff 叠加
导致慢速滚动 / 慢拖滑动条时页面被推到最底部 */
overflow-anchor: none;
}
.emptyArea {

View File

@ -22,6 +22,9 @@ export function VideoGenerationPage() {
const scrollRef = useRef<HTMLDivElement>(null);
const prevLastIdRef = useRef<string | null>(null);
const initialLoadRef = useRef(true);
// 防重入 flagloadMore + 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<string | null>(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;
});
});
}