Skip to content

fix(dashboard): 扫码登录飞书弹层 portal 到 body,修复居中被顶到视口下方 - #730

Merged
deepcoldy merged 2 commits into
deepcoldy:masterfrom
xiaoxueSunn:fix/feishu-login-modal-portal
Aug 7, 2026
Merged

fix(dashboard): 扫码登录飞书弹层 portal 到 body,修复居中被顶到视口下方#730
deepcoldy merged 2 commits into
deepcoldy:masterfrom
xiaoxueSunn:fix/feishu-login-modal-portal

Conversation

@xiaoxueSunn

@xiaoxueSunn xiaoxueSunn commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

如何发现

用户(孙晓雪)在 dashboard 给 bot 换头像,扫码登录飞书后反馈「黑屏」,需要滚动到很下面才看到二维码。

  • 第一张截图:换头像触发「服务器缺少飞书 Web 登录态」→ 弹出「扫码登录飞书」。
  • 第二张截图:整页变暗+模糊——这是 .feishu-login-overlay 的半透明遮罩,但二维码弹层被顶到视口下方,第一眼看着像黑屏/空遮罩。
  • 用户往下滚动后,二维码正常显示(「已经扫码,等待手机确认」),确认功能可用,只是弹层位置错了。

用户看到什么

触发方式 旧表现 修复后表现
dashboard 改头像/改名缺登录态 → 点「扫码登录飞书」 整屏半透明遮罩盖住,像黑屏;二维码被顶到视口下方,要向下滚动才看得到 弹层稳定居中,二维码第一眼即见

根因(经双 reviewer live 复审订正)

FeishuLoginModal 直接内联渲染在头像组件(BotAvatarControl / BotProfileIdentity)的 DOM 里,而该处位于页面容器 .page 内部。.pageanimation: dashboard-page-enter .35s ease both,关键帧动画 transformtranslateY(6px)none)。animation-fill-mode: both 下动画结束后持续「填充」最终帧,浏览器(Blink)把 .page 的 computed transform 算成 identity matrix matrix(1,0,0,1,0,0),而不是关键字 none——「非 none 的 transform」会为后代 position:fixed 建立包含块,于是弹层不再相对视口、被约束进 .page 的几何范围(本地复现 overlay top=88, height=1954),modal 被居中到 y≈881(视口外)。

注:触发者不是 .app-shelloverflow:hidden——overflow 不为 position:fixed 建立包含块;也与主题无关,light/dark 均复现(@media (prefers-reduced-motion: reduce).page 动画被禁用,此时 bug 不出现,可反向印证是该填充式动画所致)。

对照同仓库正常居中的 auth-expired-overlay——它渲染在 app 顶层(.page 之外),不受此包含块影响。

改了什么

  • bot-defaults-page.tsxFeishuLoginModal 改用 createPortal(node, document.body),把弹层挂到 body 顶层,逃出任何祖先包含块(与 auth-expired-overlay 一致,也是本仓库既有约定——sessions-pageinsights-pageskills-page 都这么做)。加 typeof document === 'undefined' 守卫兼容 SSR/无 DOM 环境。同时把根因注释订正为准确版本(.page 填充式 transform 动画,非 overflow:hidden)。
  • style.css
    • .feishu-login-overlay 追加 overflow: auto,作为极矮视口下弹层高于视口时的兜底滚动。
    • .feishu-login-modal 追加 margin: auto。纯 flex 居中(align-items:center)下,modal 高于视口时会上下等量溢出,顶部溢出无法滚回(flexbox 通用行为),标题/关闭按钮够不着;margin:auto 在负可用空间下归 0、子元素落到滚动起点使顶部可达,正常视口仍由 auto margin 居中。这样 overflow:auto 兜底才真正生效。

两处 FeishuLoginModal 调用点(头像、改名)共用同一组件,一处修复全部生效。

影响面

纯 dashboard 前端改动:.tsx 仅调整渲染挂载点 + 注释,.css 仅两条声明。不涉及 daemon / worker / 任何 CLI 适配器 / 后端逻辑,构建产物只进 dashboard bundle。portal 后经核对:.feishu-login-* 专属样式全为纯类名或 :where() 列表(不依赖 DOM 位置)、:root 主题变量仍继承、z-index 仍生效、backdrop/close/retry 事件语义不变;retry 按钮圆角由 999px→12px 属向对标的 auth-expired-dialog 收敛(后者本就在 .page 外、同为 12px),不是回归。

测试验证

  • pnpm build:✅(domain audit + tsc --noEmit + dashboard bundle + dist audit 全过)。
  • dashboard 定向 3 文件 / 15 tests:✅;git diff --check:✅。
  • Chromium live 复测(reviewer 用真实构建后的 style.css,未注入补丁):
    • 正常视口 1280×599(dark/light):overlay = 视口(top=0,height=599),modal top=116.5,height=366,横纵中心误差 0px
    • 极矮视口 1280×260 滚动起点:modal top=20,关闭按钮/标题顶部完整可达(overlay scrollHeight=406, maxScrollTop=146)。
    • 滚动到底:retry 按钮 / modal 底部完整可达——margin:auto 同时覆盖正/负 free-space 场景。
    • 页面祖先实算 transform: matrix(1,0,0,1,0,0),与订正后的源码注释一致;console 无新增错误。

截图依次为:旧 inline(弹层被顶到视口下方)、portal 后正常视口居中、260px 极矮视口滚动起点顶部可达、滚动终点底部可达。

改头像/改名缺登录态时弹出的「扫码刷新飞书登录态」弹层,此前内联渲染在
头像组件的 DOM 深处,而该处位于 .app-shell(height:100dvh + overflow:hidden
的滚动容器)内部。position:fixed 被这个滚动容器约束,弹层被顶到视口下方,
用户第一眼看到的是盖满屏的半透明遮罩(像黑屏),要往下滚动才看得到二维码。

改为 createPortal 到 document.body(与 auth-expired-overlay 一致,也是本
仓库既有约定),弹层回到视口正中稳定居中。overlay 追加 overflow:auto 作为
极矮视口下的兜底,弹层高于视口时仍可滚动。

Co-Authored-By: Claude <noreply@anthropic.com>
@xiaoxueSunn
xiaoxueSunn requested a review from deepcoldy as a code owner August 4, 2026 08:59
- style.css: .feishu-login-modal 加 margin:auto。原 overflow:auto 兜底在
  纯 flex 居中(align-items:center)下失效——modal 高于视口时上下等量溢出、
  顶部滚不回,标题/关闭按钮够不着。auto margin 负可用空间归 0 使子元素落到
  滚动起点、顶部可达,正常视口仍居中。
- bot-defaults-page.tsx: 订正 portal 根因注释。真正建立 fixed 包含块的是祖先
  .page 的 dashboard-page-enter 动画(fill-mode:both 填充期 transform 被算成
  identity matrix,非关键字 none),与主题无关;不是 .app-shell 的 overflow:hidden
  (overflow 不建立 fixed 包含块)。
@deepcoldy
deepcoldy merged commit d990ffe into deepcoldy:master Aug 7, 2026
deepcoldy added a commit to xiaoxueSunn/botmux that referenced this pull request Aug 7, 2026
master 前进到 7ec6d4b(含 deepcoldy#775/deepcoldy#730/deepcoldy#750/deepcoldy#762),与 deepcoldy#597 二次冲突 6 文件。
本 worktree merge origin/master 解冲突(admin-squash 时拍平)。

6 文件逐点取舍:
- core/reply-target.ts: union 两套并存的 per-turn 记录——deepcoldy#597 turnReplyContexts(frozen dispatch target)+ deepcoldy#750 replyTargets(mention-back 参与者窗口)。dedupeParticipants/buildTurnParticipantsFrom/collectTurnWindowParticipants/frozenReplyContextForTurn 全保留。
- cli.ts: replyTargets 类型取 master 更全版 + 保 deepcoldy#597 codex-app 字段;flash footer 取 master 对象式(deepcoldy#762),删除会话用 result.mode(非 master 误用的 result.via);replyTargetSenderOpenId 回退链 union:VC → deepcoldy#597 frozenTurnDispatch → deepcoldy#750 turnReplyTarget.senderOpenId → legacy。
- daemon.ts: 三处 registration-race 取 deepcoldy#597 结构化 claimNewDaemonSession + routeToCanonicalOwner/handleThreadReplyAdmitted;destructure union routeToCanonicalOwner + senderIsBot。修真实回归:CAS-loser 经 handleThreadReplyAdmitted 重算 post @s 只读 data.message 漏 ctx.forwardSeedData → 双 race 丢种子转发 post @。两处重算补 forward-seed 参数(codex 合并不变量deepcoldy#1)。
- 3 测试文件: import union;initial-passthrough-ownership 取 deepcoldy#597 claim 断言;cli-send-hook-context + daemon-turn-reply-sender-wiring 迁写到 deepcoldy#597 模型(forwardSeed 重算)并保 deepcoldy#750 mention-back 断言并存。

验证: pnpm build✅; 受影响套件全绿(reply-target-fallback 41/daemon-turn-reply-sender-wiring 7/cli-send-hook-context 14/send-policy 46/initial-passthrough-ownership 8/daemon-rename-route 55/command-handler 235/transfer-session 71/dashboard-create-session 41/trigger-session-root-message 41/restore-zombie-close 33/scheduler-silent-execute 24/session-resume 39); deepcoldy#597 核心 steer worker-routing 8+bridge 66+dispatch 6+transfer-gate 2+initial-user-turn 28。codex-app-runner 1 项既有 bounded-history flake 无关。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants