统一 AGC launcher 页面高度,修复帮助页底部被裁
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 2m29s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 2m31s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 3m2s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m49s
Project CI / Backend tests (pull_request) Failing after 19s
Project CI / Repository checks (pull_request) Failing after 30s
Project CI / Frontend tests (pull_request) Successful in 3m16s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m37s
Project CI / Native shell tests (pull_request) Failing after 5m2s

- styles.css:新增页面高度契约,.launcher-main 改纵向 flex 列、页面根节点 flex: 1 1 auto,帮助页自带滚动
- src/view/home/index.tsx:首页根节点去掉 h-screen 与 h-[calc(100vh-32px)],改用外壳分配高度的钩子类
- src/view/template-library/index.tsx:模板库去掉 JS 量父级高度与内联 height,改用外壳分配高度的钩子类
- decision-log.md、pitfalls.md:记录页面高度契约与自绘标题栏里自己算 100vh 的排障口径

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
2026-10-02 13:46:33 +08:00
parent b8a659c12f
commit 50925d96eb
5 changed files with 47 additions and 38 deletions
+27
View File
@@ -1332,6 +1332,19 @@ textarea {
overflow: hidden;
}
/*
* 页面高度契约:`.launcher-main` 是纵向 flex 列,页面根节点自己 `flex: 1 1 auto` 吃掉
* 舞台剩余高度(有横幅时自动少掉横幅那一份),页面内部的 `height: 100%`、内层滚动容器
* 才有确定高度可用。
*
* 页面根节点上不要再写 `100vh` / `100dvh` / `calc(100vh - 32px)`:整窗高度含自绘标题栏,
* 比真实舞台高一个标题栏(窗口 100vh=800、舞台 750),底部会被裁掉,横幅也会被重复扣一次。
* 窗口外壳用 `height: 100%` 覆盖这里的 `100dvh`,让页面贴合 `.window-chrome__content`
* 的真实舞台;没有窗口外壳时 `100dvh` 才是正确值。
*/
.launcher-main:has(.launcher-home-page),
.launcher-main:has(.launcher-help-page),
.launcher-main:has(.launcher-template-library),
.launcher-main:has(.launcher-projects-page) {
display: flex;
flex-direction: column;
@@ -1340,6 +1353,20 @@ textarea {
padding-bottom: 0;
}
.launcher-main > .launcher-home-page,
.launcher-main > .launcher-help-page,
.launcher-main > .launcher-template-library {
flex: 1 1 auto;
height: auto;
min-height: 0;
}
/* 帮助页没有内层滚动容器:占满剩余高度后自己滚动,底部留出与外壳一致的呼吸位。 */
.launcher-main > .launcher-help-page {
overflow-y: auto;
padding-bottom: 42px;
}
.launcher-page header {
display: flex;
align-items: flex-end;
@@ -209,11 +209,7 @@ export default function HomeView({
}
return (
<div
className={`platform-theme platform-theme--light overflow-x-hidden overflow-y-auto bg-(image:--platform-body-fill) pb-12 text-(--platform-text-strong) ${
hasPromo ? 'h-[calc(100vh-32px)]' : 'h-screen'
}`}
>
<div className="launcher-home-page platform-theme platform-theme--light overflow-x-hidden overflow-y-auto bg-(image:--platform-body-fill) pb-12 text-(--platform-text-strong)">
<section
className={`grid content-start justify-items-center gap-3.5 px-6 pb-4.5 pt-20 max-[760px]:gap-4 max-[760px]:px-3.5 max-[760px]:pb-8.5 max-[760px]:pt-21 ${
hasPromo ? 'pt-28 max-[760px]:pt-21' : ''
@@ -152,8 +152,6 @@ export default function TemplateLibraryView({
const gridRef = useRef<FixedSizeGrid>(null);
const viewportRef = useRef<HTMLDivElement | null>(null);
const sectionRef = useRef<HTMLElement | null>(null);
const [sectionHeight, setSectionHeight] = useState<number | null>(null);
const [viewportSize, setViewportSize] = useState({ width: 0, height: 0 });
const showEmptyLibrary = status === 'ready' && templates.length === 0;
@@ -162,35 +160,10 @@ export default function TemplateLibraryView({
const showGrid = status === 'ready' && visibleTemplates.length > 0;
/**
* 页面高度按**父级实测高度**定,不用百分比也不用 100vh。
*
* 外壳样式 `.launcher-main > .platform-theme { height: 100% }` 特异性高于 Tailwind
* 工具类,而它的百分比在 `.launcher-shell { min-height: 100vh }` 这条链上是不定高,
* 页面会退化成内容高度(虚拟网格视口高度 0、卡片区空白);`100vh` 又比真实舞台高
* 一个标题栏(窗口 100vh=800、舞台 750),底部会被裁掉。这里直接量父级。
* 页面高度不再自己量:`.launcher-main:has(.launcher-template-library)` 把外壳改成纵向
* flex 列,`flex: 1 1 auto` 让这一页拿到确定高度(有横幅时自动少掉横幅那一份),
* 下面的网格视口才能用 `flex-1` 量出真实尺寸。
*/
useEffect(() => {
const section = sectionRef.current;
const parent = section?.parentElement;
if (!section || !parent) {
return;
}
const apply = () => {
const height = Math.round(parent.getBoundingClientRect().height);
if (height > 0) {
setSectionHeight((current) => (current === height ? current : height));
}
};
apply();
if (typeof ResizeObserver === 'undefined') {
window.addEventListener('resize', apply);
return () => window.removeEventListener('resize', apply);
}
const observer = new ResizeObserver(apply);
observer.observe(parent);
return () => observer.disconnect();
}, []);
useEffect(() => {
const element = viewportRef.current;
if (!element) {
@@ -279,9 +252,7 @@ export default function TemplateLibraryView({
return (
<section
ref={sectionRef}
style={sectionHeight ? { height: `${sectionHeight}px` } : undefined}
className="platform-theme platform-theme--light flex min-h-0 w-full flex-col gap-3.5 overflow-hidden px-6 pb-5 pt-5 max-[760px]:px-4"
className="launcher-template-library platform-theme platform-theme--light flex min-h-0 w-full flex-col gap-3.5 overflow-hidden px-6 pb-5 pt-5 max-[760px]:px-4"
aria-label="模板库"
>
<header className="flex flex-wrap items-center gap-3">