@@ -1012,7 +1023,7 @@ function ReplyBar({
onChange={setBody}
placeholder="回复内容(Markdown)"
density="roomy"
- autoFocus={narrow}
+ autoFocus
error={error}
submit={{
label: '发送',
diff --git a/client/electron/test/manual/wide-regression.mjs b/client/electron/test/manual/wide-regression.mjs
index 7d3214b..f882bb0 100644
--- a/client/electron/test/manual/wide-regression.mjs
+++ b/client/electron/test/manual/wide-regression.mjs
@@ -63,6 +63,67 @@ const of = await page.evaluate(() => ({
}));
chk('无横向溢出', of.s <= of.d, `doc=${of.d} scroll=${of.s}`);
+/*
+ * 折叠策略在**宽屏**同样生效(2026-09-14 用户:「我发现宽屏布局也有邮件内容
+ * 显示区域过小的问题,宽屏也同步窄屏的折叠策略吧」)。
+ *
+ * 为什么必须量真实的盒子:上一版宽屏是「恒展开」,而头部 139px(17%) + 回复框
+ * 246px(31%) 把正文挤到只剩 385px(48%)。那时**没有任何判据会红** ——
+ * narrow-layout 只判窄屏,而宽屏这边一条断言都没有。
+ */
+const fold = await page.evaluate(() => {
+ const px = n => Math.round(n);
+ const exp = document.querySelector('[aria-expanded]');
+ const header = exp ? exp.closest('.glass-card') : null;
+ let scroller = document.querySelector('.markdown');
+ while (scroller && !['auto', 'scroll'].includes(getComputedStyle(scroller).overflowY)) {
+ scroller = scroller.parentElement;
+ }
+ const fab = document.querySelector('[data-testid="reply-fab"]');
+ const pane = document.querySelector('#root > div > div:last-child');
+ const R = el => (el ? el.getBoundingClientRect() : null);
+ const hb = R(header), sb = R(scroller), fb = R(fab), pb = R(pane);
+ return {
+ vh: px(window.innerHeight),
+ expanded: exp ? exp.getAttribute('aria-expanded') : null,
+ headerH: hb ? px(hb.height) : 0,
+ scrollerH: sb ? px(sb.height) : 0,
+ fab: fb ? { x: px(fb.x), y: px(fb.y), w: px(fb.width), h: px(fb.height) } : null,
+ pane: pb ? { x: px(pb.x), y: px(pb.y), w: px(pb.width), h: px(pb.height) } : null,
+ hasTextarea: !!document.querySelector('textarea')
+ };
+});
+
+chk('宽屏头部也默认收起', fold.expanded === 'false', String(fold.expanded));
+chk(
+ '宽屏正文占视口 80% 以上',
+ fold.scrollerH / fold.vh >= 0.8,
+ `${Math.round((fold.scrollerH / fold.vh) * 100)}%(收起态实测 90%)`
+);
+chk('宽屏回复框也默认收起、只剩悬浮球', fold.fab !== null && !fold.hasTextarea);
+chk(
+ '悬浮球在详情栏内(不是视口右下角)',
+ fold.fab && fold.pane &&
+ fold.fab.x >= fold.pane.x && fold.fab.y >= fold.pane.y &&
+ fold.fab.x + fold.fab.w <= fold.pane.x + fold.pane.w + 1 &&
+ fold.fab.y + fold.fab.h <= fold.pane.y + fold.pane.h + 1,
+ `球=${JSON.stringify(fold.fab)} 栏=${JSON.stringify(fold.pane)}`
+);
+
+// 点标题行要真的展开(“默认收起”不能变成“永远打不开”)
+await page.locator('[aria-expanded]').click();
+await page.waitForTimeout(500);
+const afterExpand = await page.evaluate(() => ({
+ expanded: document.querySelector('[aria-expanded]')?.getAttribute('aria-expanded'),
+ hasFrom: document.body.innerText.includes('发件')
+}));
+chk('宽屏点标题行能展开并看到收发件人', afterExpand.expanded === 'true' && afterExpand.hasFrom);
+
+// 点悬浮球要能打开输入框
+await page.locator('[data-testid="reply-fab"]').click();
+await page.waitForTimeout(500);
+chk('宽屏点悬浮球能展开输入框(带焦点)', await page.evaluate(() => document.activeElement?.tagName === 'TEXTAREA'));
+
console.log('\nissues:', issues.length ? issues : '无');
console.log(failed.length === 0 ? '\n宽屏回归:全部通过' : `\n宽屏回归:${failed.length} 项失败`);
diff --git a/client/electron/test/narrow-layout.test.mjs b/client/electron/test/narrow-layout.test.mjs
index b9a35c2..efda5df 100644
--- a/client/electron/test/narrow-layout.test.mjs
+++ b/client/electron/test/narrow-layout.test.mjs
@@ -182,20 +182,27 @@ for (const f of wide) {
check(`${f} 没有裸 px-6(应为 px-4 md:px-6)`, bare.length === 0, `发现 ${bare.length} 处`);
}
-// 7) 窄屏阅读:顶部只留标题、底部收起为悬浮球
+// 7) 阅读态:顶部只留标题、底部收起为悬浮球(**窄屏与宽屏同一套**)
//
// 用户(2026-09-14):「窄屏页面阅读邮件时,顶部的邮件信息和底部的输入框等
// 占用了绝大部分页面,用户只能通过中间的一小块看邮件……上面缩为窄栏,只显示
// 邮件标题,点击展开显示完整信息,底部一个悬浮的聊天图标的球,点一下展开输入框」。
//
-// 这两处坏掉的形态很隐蔽:组件照样渲染、宽屏一切正常,只是窄屏可读区被挤成一条缝。
-// 所以在结构层钉住「窄屏默认收起 + 有展开出口 + 有收回归口」。
+// ★ 同日追加:「我发现宽屏布局也有邮件内容显示区域过小的问题,宽屏也同步
+// 窄屏的折叠策略吧」。上一版是「窄屏收起 / 宽屏恒展开」—— 那条只看了**横向**,
+// 而实测 1280x800 下头部 139px(17%) + 回复框 246px(31%),正文只剩 385px(48%)。
+// 所以现在两种宽度**同一套默认值**,判据也改成钉「不再按窄宽分叉」。
+//
+// 这两处坏掉的形态很隐蔽:组件照样渲染、只是可读区被挤成一条缝。
+// 所以在结构层钉住「默认收起 + 宽窄同一套 + 有展开出口 + 有收回归口」。
const mv = read('../src/components/MailView.tsx');
check(
- '头部窄屏默认收起(useState(!narrow) + 断点变化时重置)',
- /useState\(!narrow\)/.test(mv) && /setOpen\(!narrow\)/.test(mv),
- '缺少「窄屏默认收起」或跨断点重置 —— 自动隐藏只在首次生效'
+ '头部默认收起,且**不再按窄宽分叉**(旧写法 useState(!narrow) 已消失)',
+ /const \[open, setOpen\] = useState\(false\)/.test(mv) &&
+ /useEffect\(\(\) => setOpen\(false\), \[narrow\]\)/.test(mv) &&
+ !/useState\(!narrow\)/.test(mv),
+ '头部还在按窄/宽分叉默认值 —— 宽屏竖向空间一样不够'
);
check(
'头部收起态保留标题与状态点,展开才有完整信息',
@@ -204,15 +211,28 @@ check(
mv.includes('{open &&
{children}
}')
);
check(
- '回复框窄屏收起为悬浮球(reply-fab),点开才展开',
+ '回复框默认收起为悬浮球(reply-fab),框自身不带 narrow 条件',
mv.includes('data-testid="reply-fab"') &&
- mv.includes('narrow && !open') &&
+ /if \(!open\) \{/.test(mv) &&
+ !/narrow && !open/.test(mv) &&
mv.includes('absolute bottom-4 right-4 z-20') &&
mv.includes('