fix: mobile hub AI keyboard layout and instance top nav scroll

Sync hub shell to visualViewport when the keyboard opens to prevent white screen above chat input. Make instance tabs horizontally scrollable with active tab centered on phone.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-09 17:16:00 +08:00
parent 9aba8ec645
commit 02d2a6c70b
9 changed files with 159 additions and 23 deletions
+66
View File
@@ -652,6 +652,7 @@
el.classList.toggle("hidden", el.id !== pageId);
});
document.body.classList.toggle("hub-page-ai", page === "ai");
syncHubAiMobileViewport();
if (page === "monitor") startMonitorPoll();
else stopMonitorPoll();
if (page === "settings") loadSettingsUI();
@@ -1005,8 +1006,73 @@
applyAiMobileTab();
}
let syncHubAiMobileViewport = () => {};
function initHubAiMobileViewport() {
const mq = window.matchMedia("(max-width: 720px)");
const shell = document.querySelector(".app-shell");
const chatInput = document.getElementById("ai-chat-input");
if (!shell || !window.visualViewport) {
syncHubAiMobileViewport = () => {};
return;
}
const scrollChatToEnd = () => {
const box = document.getElementById("ai-chat-messages");
if (box) requestAnimationFrame(() => { box.scrollTop = box.scrollHeight; });
};
syncHubAiMobileViewport = () => {
const onAi = document.body.classList.contains("hub-page-ai");
if (!onAi || !mq.matches) {
shell.style.removeProperty("height");
shell.style.removeProperty("max-height");
shell.style.removeProperty("width");
shell.style.removeProperty("transform");
document.documentElement.style.removeProperty("--hub-vvh");
document.body.classList.remove("hub-ai-keyboard-open");
return;
}
const vv = window.visualViewport;
const h = Math.max(240, Math.round(vv.height));
const top = Math.round(vv.offsetTop || 0);
const left = Math.round(vv.offsetLeft || 0);
document.documentElement.style.setProperty("--hub-vvh", `${h}px`);
shell.style.height = `${h}px`;
shell.style.maxHeight = `${h}px`;
shell.style.width = `${Math.round(vv.width)}px`;
shell.style.transform =
top > 0 || left > 0 ? `translate(${left}px, ${top}px)` : "";
const keyboardLikely =
top > 0 || h < window.innerHeight * 0.82 || document.activeElement === chatInput;
document.body.classList.toggle("hub-ai-keyboard-open", keyboardLikely);
};
window.visualViewport.addEventListener("resize", syncHubAiMobileViewport);
window.visualViewport.addEventListener("scroll", syncHubAiMobileViewport);
window.addEventListener("resize", syncHubAiMobileViewport);
window.addEventListener("orientationchange", () => {
setTimeout(syncHubAiMobileViewport, 80);
});
if (chatInput) {
chatInput.addEventListener("focus", () => {
syncHubAiMobileViewport();
scrollChatToEnd();
setTimeout(syncHubAiMobileViewport, 50);
setTimeout(syncHubAiMobileViewport, 280);
});
chatInput.addEventListener("blur", () => {
setTimeout(syncHubAiMobileViewport, 80);
setTimeout(syncHubAiMobileViewport, 320);
});
}
syncHubAiMobileViewport();
}
function initMobileLayout() {
initAiMobileTabs();
initHubAiMobileViewport();
let resizeTimer = null;
let wasMobile = isMobileLayout();
window.addEventListener("resize", () => {