Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/STATUS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 2026-11-02 — 应用会话缩放后内容区宽度分配

- PR #2581 同步目标分支 `037d6037a1cf6a906caf51f3657b7c688ac42bd7`,完整保留本修复与整窗蒙层双方状态记录;业务文件自动合并,并逐文件核对与各自提交内容一致。组合回归 5 组 / 52 项、前端全量 lint/typecheck、Client 生产构建通过;部署与 Windows 原机验收继续单列。
- 额外根因:桌面嵌入视口变窄时仍为应用侧栏保留 240px,对话区域的标题下拉覆盖返回按钮,进一步缩放后按钮进入裁剪范围。16px/24px 字体对照的失败组合一致。
- 767px 以下桌面内容区复用已有历史 region,菜单与标题访问应用历史,对话获得完整宽度;放宽后恢复侧栏偏好,跨过宽标题断点时关闭历史。字体、按钮视觉和来源返回解析沿用原实现。
- 旧实现新增回归 4 项失败;修正后 3 组 / 38 项通过。macOS Electron 192 组设备缩放、页面缩放、字体与宽度组合全部通过,旧版对应 28 组返回命中/重叠失败;连续窗口变宽/恢复及原生鼠标、Esc、返回操作通过。
- 全量前端 lint/typecheck、Client 生产构建、i18n、架构与差异检查通过。基线 `780723543c408013d0011a2d8053fdc6bb712420`,见 [验证记录](verification/app-chat-scaled-layout-20261102.md)。部署、Windows 系统 DPI/真实字体与安装版真实账号验收待执行。

## 2026-11-02 — 桌面弹窗整窗统一蒙层

- 桌面内的 Dialog、OriginalDialog、AlertDialog 统一采用黑色 40% 蒙层,配合现有 Desktop 窗口级模态协议,让左侧会话栏与右侧内容一起变暗,弹窗保持清晰。实现直接调整 Client 共用展示函数,采用现有 Modal 规范的蒙层取值。
Expand Down
42 changes: 42 additions & 0 deletions docs/verification/app-chat-scaled-layout-20261102.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 应用会话缩放后侧栏挤占内容区修复

## 基线

- 版本:`dataelement/bisheng:feat/bisheng-work-120-main@780723543c408013d0011a2d8053fdc6bb712420`,本轮 fetch 核对。
- 独立目录 `.worktrees/bisheng-app-chat-scale-20261102`,分支 `codex/app-chat-scaled-layout`。
- 本轮改动位于网页 AppRoot。桌面参考沿用已核对 `V1.2.0@09c22fe34e5c89193c15387685810fbaa45e73fc`;本机 Harness 依赖 `0.2.1-rc.2`,Electron `43.0.0`,pnpm `9.15.9`。

## 发现和修正

用户反馈 Windows 当前仍出现问题,提出字体字号可能相关。系统缩放与客户端页面缩放具体值暂无现场信息。

当前源码在桌面嵌入模式保持桌面身份,即使内容视口很窄也会保留 240px 应用侧栏。紧凑标题栏随后仅获得剩余宽度。实际 Electron 测量:359px 的应用视口分成 240px 侧栏和 119px 对话区域;居中的标题下拉及箭头与返回按钮重叠,点击命中进入下拉元素。进一步放大页面后,返回按钮落到内容裁剪边界之外。

对照把标题从 16px Arial 改为 24px 等宽字体,失败组合相同;直接复现因素是可用内容宽度与固定侧栏并排。该实验支持缩放触发布局缺陷,Windows 用户原机的确切触发参数仍待复测确认。

AppRoot 现在按现有 767px 断点选择内容导航:窄桌面内容区复用已有历史 region,通过菜单或标题访问应用卡片与会话列表;对话获得完整宽度。放宽到并排布局时恢复原有侧栏偏好;窄布局新建对话保留该偏好。跨过 1023px 宽布局断点时关闭紧凑历史,恢复窗口后由用户重新打开。浏览器移动端继续使用系统菜单。原字体、字号、按钮视觉、来源返回解析与宿主代码沿用当前实现。

## 验证

- 新增回归在旧实现有 4 项失败;最终 AppRoot、来源解析和发现广场入口共 3 组 / 38 项通过,覆盖窄桌面菜单开关、返回、侧栏偏好、创建对话、最大化/恢复、移动系统菜单。
- macOS Electron 隔离实验使用真实 AppRoot、MobileNav、历史列表和生产 CSS,数据服务与 SideNav 内容采用夹具。模型以 1278→1920→1278 物理像素窗口、280 DIP 宿主侧栏计算内容区;设备缩放 100/125/150/200%、页面缩放 100/125/150/200%、侧栏偏好展开/收起、标题 16/24px,共 192 组。旧版 28 组返回命中或区域重叠失败;修正版 192 组按钮命中和历史开关几何检查全部通过,控制台错误为零。
- 另行连续改变同一窗口内容宽度 359→1200→359→900→359,确认窄视口对话 x=0、宽视口按原侧栏布局。原生鼠标事件打开历史、Esc 关闭、点击返回到 `/square?type=apps` 全部通过。
- Client / Platform / UI / file-viewers 全量 lint 通过;类型检查 Client 1513 个、Platform 620 个 strict 文件和 file-viewers 均通过。Client 生产构建、i18n、架构守卫和差异检查通过。
- 初始环境默认 pnpm 版本尝试自动核对依赖时因共享目录保护退出。后续使用项目版本 pnpm 9.15.9 完成构建,并通过现有 npm scripts 执行等效质量门;依赖声明和锁文件沿用当前基线。

设备缩放模拟、页面缩放和字体压力对照分别记录,macOS 上的字体替代与设备比例实验属于布局验证。Windows 系统 DPI、Windows 真实字体、安装包和真实账号端到端验收待执行。前轮已核对 13001 仍提供旧版返回/历史脚本,部署状态与本轮额外源码修复分别记录;用户人工复现及 TC-120-DSQ-001/012 的 Windows 失败状态继续保留。

## 交付

本轮修复及自动验证完成。PR、网页部署和 Windows 原机验收各自按后续证据更新。主工作区证据:`outputs/app-chat-font-scale-20261102/`;核心文件 `summary.json`、`cycle.json`、`regression.log`、各项质量日志及 `fixed-200-percent.png`。

## Code review

按 hotfix 范围自检:导航与宽度采用同一个布局条件;菜单和标题共享已有非模态历史列表;侧栏偏好与窄布局临时开关分别保存;新建和窗口恢复有回归;真实账号与 Windows 运行结果保持单列。源码、权限及依赖范围保持应用导航边界。

## PR #2581 目标分支同步

- 在线核对 PR head `a6c3aec9d751e3d8964fa6188e46aa103d96cc11`;目标分支新增 PR #2580,当前目标为 `037d6037a1cf6a906caf51f3657b7c688ac42bd7`。
- 冲突集中在 STATUS 的同一插入位置,现同时保留应用会话缩放和整窗弹窗蒙层记录。AppRoot 两个文件与本修复原提交逐字节一致,弹窗展示两个文件与目标分支逐字节一致。
- 同步后的 AppRoot、来源解析、发现广场入口、DialogPresentation、KnowledgeSpaceDialog 组合回归 5 组 / 52 项通过。全量前端 lint/typecheck、Client 生产构建通过;Client 1513 / Platform 620 个严格类型文件通过。依赖和锁文件沿用原版本。
- 网页部署及 Windows 安装版真实账号验收按原边界记录。同步验证日志位于主工作区 `outputs/app-chat-font-scale-20261102/merge-2581/`。
49 changes: 47 additions & 2 deletions src/frontend/client/src/routes/AppRoot.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const mockSystemMenu = jest.fn();
const mockRows = jest.fn();
let mockMobile = false;
let mockCompact = true;
let mockNarrow = false;
let mockSidebarVisible = true;
let mockMerged = true;
let mockReturnTo = '/square?type=apps';
Expand Down Expand Up @@ -38,7 +39,7 @@ jest.mock('~/pages/appChat/store/atoms', () => ({ currentChatState: 'chat' }));
jest.mock('~/hooks', () => ({
useLocalize: () => (key: string) => key,
useAuthContext: () => ({ isAuthenticated: true }),
useMediaQuery: () => mockCompact,
useMediaQuery: (query: string) => query === '(max-width: 767px)' ? mockNarrow : mockCompact,
usePrefersMobileLayout: () => mockMobile,
useNewConvo: () => ({ newConversation: jest.fn() }),
}));
Expand All @@ -65,7 +66,7 @@ jest.mock('~/components/ui/Tooltip2', () => ({ Tooltip: () => null, TooltipProvi
jest.mock('bisheng-icons', () => ({ Outlined: { SidebarMenu: () => null, Plus: () => null, Down: () => null } }));

beforeEach(() => {
mockMobile = false; mockCompact = true; mockSidebarVisible = true; mockMerged = true;
mockMobile = false; mockCompact = true; mockNarrow = false; mockSidebarVisible = true; mockMerged = true;
localStorage.clear(); sessionStorage.clear(); mockReturnTo = '/square?type=apps';
});

Expand Down Expand Up @@ -152,6 +153,7 @@ it('returns keyboard focus to the title on Escape and releases its listener', ()

it('retains the mobile system menu destination', () => {
mockMobile = true;
mockNarrow = true;
render(<AppRoot />);
fireEvent.click(screen.getByTestId('mobile-header-left-action'));
expect(mockSystemMenu).toHaveBeenCalledWith(true);
Expand Down Expand Up @@ -191,3 +193,46 @@ it.each(['/square?type=apps', '/apps', '/apps/explore'])('returns to %s with his
fireEvent.click(screen.getByRole('button', { name: 'com_ui_go_back' }));
expect(mockNavigate).toHaveBeenCalledWith(target, { replace: target === '/apps' });
});

it.each([true, false])('uses full-width history navigation in narrow desktop content with sidebar=%s', (visible) => {
mockNarrow = true;
mockSidebarVisible = visible;
render(<AppRoot />);
expect(screen.queryByText('App sidebar')).toBeNull();
const menu = screen.getByTestId('mobile-header-left-action');
expect(menu).toHaveAttribute('aria-expanded', 'false');
fireEvent.click(menu);
expect(screen.getByRole('region', { name: 'com_app_chat_sidebar_title' })).toBeVisible();
expect(menu).toHaveAttribute('aria-expanded', 'true');
fireEvent.click(screen.getByRole('button', { name: 'com_ui_go_back' }));
expect(mockNavigate).toHaveBeenCalledWith('/square?type=apps', { replace: false });
fireEvent.click(menu);
expect(screen.queryByRole('region')).toBeNull();
});

it.each([true, false])('preserves docked sidebar choice %s through narrow content and new chat', (visible) => {
mockSidebarVisible = visible;
const view = render(<AppRoot />);
mockNarrow = true;
view.rerender(<AppRoot />);
fireEvent.click(screen.getByTestId('mobile-header-left-action'));
fireEvent.click(screen.getByTestId('mobile-header-new-chat-button'));
expect(screen.queryByRole('region')).toBeNull();
mockNarrow = false;
view.rerender(<AppRoot />);
expect(screen.getByText('App sidebar').parentElement).toHaveClass(visible ? 'w-[240px]' : 'w-0');
});

it('closes compact history when maximized into the wide layout', () => {
mockNarrow = true;
const view = render(<AppRoot />);
fireEvent.click(screen.getByRole('button', { name: 'New conversation' }));
mockNarrow = false;
mockCompact = false;
view.rerender(<AppRoot />);
mockNarrow = true;
mockCompact = true;
view.rerender(<AppRoot />);
expect(screen.queryByRole('region')).toBeNull();
expect(screen.getByTestId('mobile-header-left-action')).toHaveAttribute('aria-expanded', 'false');
});
20 changes: 16 additions & 4 deletions src/frontend/client/src/routes/AppRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export default function AppRoot() {
/** 与 tailwind touch-mobile 一致:768–1023 仍用合并顶栏,避免仅 isTabletOrMobile 时出现 absolute 悬浮菜单 + 内层标题双行 */
/** ≤1023:合并顶栏 + 禁止左上角 absolute 悬浮钮(与 tailwind touch-mobile 一致) */
const isAppChatCompact = useMediaQuery('(max-width: 1023px)');
const isNarrowContent = useMediaQuery('(max-width: 767px)');
// Embedded desktop panes use the existing history region below this width,
// leaving the full content width available to the header and conversation.
const usesHistoryNavigation = isNarrowContent && !isTabletOrMobile;
const sidebarWidth = 240;
const isAppConversationRoute = /^\/app\/[^/]+\/[^/]+\/[^/]+(?:\/|$)/.test(location.pathname);
/** AppRoot 仅挂在 /app/*;含 `/app/:fid/:type` 入口,不能用仅三段的 isAppConversationRoute 控制顶栏 */
Expand All @@ -51,6 +55,10 @@ export default function AppRoot() {
type AppSurfaceLocationState = { appSurfaceReturn?: string };

const toggleSidebar = () => setSidebarVisible((prev) => !prev);
const handleDesktopMenu = () => {
if (usesHistoryNavigation) setAppHistoryOpen(!appHistoryOpen);
else toggleSidebar();
};
const handleGoBack = () => {
const pathSegments = location.pathname.split('/').filter(Boolean);
const appSegmentIndex = pathSegments.indexOf('app');
Expand Down Expand Up @@ -129,6 +137,10 @@ export default function AppRoot() {
scrollLockPrevRef.current = null;
});

useEffect(() => {
if (isAppChatCompact === false) setAppHistoryOpen(false);
}, [isAppChatCompact]);

if (!isAuthenticated) {
return null;
}
Expand All @@ -155,7 +167,7 @@ export default function AppRoot() {
>

{/* Desktop/Tablet sidebar */}
{!isTabletOrMobile && (
{!isTabletOrMobile && !usesHistoryNavigation && (
<div
className={cn(
'transition-all duration-300 overflow-hidden flex-shrink-0',
Expand Down Expand Up @@ -198,12 +210,12 @@ export default function AppRoot() {
<div className="shrink-0 overflow-hidden rounded-t-xl bg-white">
<MobileNav
variant="chat"
navVisible={sidebarVisible}
setNavVisible={setSidebarVisible}
navVisible={usesHistoryNavigation ? appHistoryOpen : sidebarVisible}
setNavVisible={usesHistoryNavigation ? setAppHistoryOpen : setSidebarVisible}
persistNavVisibleInLocalStorage={false}
navigateToNewChatPath={false}
onNewChat={handleCreateNewAppChat}
onMenuClick={isTabletOrMobile ? undefined : toggleSidebar}
onMenuClick={isTabletOrMobile ? undefined : handleDesktopMenu}
appSurfaceBackAction={handleGoBack}
appHistoryDropdownOpen={appHistoryOpen}
onToggleAppHistoryDropdown={() => setAppHistoryOpen((o) => !o)}
Expand Down
Loading