diff --git a/design-qa.md b/design-qa.md index 741649a..b18503e 100644 --- a/design-qa.md +++ b/design-qa.md @@ -1,65 +1,61 @@ # Design QA -- Source visual truth: `/workspace/scratch/6e52996114b8/upload/661cd6c2-dfc4-46ea-ac8b-17630be6c35e.png` -- Source pixel dimensions: `462 × 273`. -- Intended implementation viewport: dark-theme mobile workspace, focused content width around `360px` CSS pixels. -- Implementation screenshot: unavailable. -- CSS size and density normalization: unavailable because the implementation could not be opened in the cloud browser. -- State: 形态图片页包含镜头定位与完整筛选区。 +- Source visual truth: `/workspace/scratch/6e52996114b8/upload/f7dbeb9e-a47c-4e09-9b5c-c5f885b0d708.png` +- Source pixel dimensions: `543 × 978`. +- Browser-rendered open-state comparison: `/workspace/scratch/mobile-sidebar-open-comparison-20260908.jpg` +- Browser-rendered closed-state comparison: `/workspace/scratch/mobile-sidebar-closed-comparison-20260908.jpg` +- Comparison screenshot dimensions: `1363 × 936`. +- Implementation CSS viewport: `543 × 936`, device scale factor `1`. +- Density normalization: source and implementation are both displayed at `543px` CSS width; the source's bottom `42px` is outside the comparison viewport and contains no sidebar structure needed for this review. +- State: dark-theme project list, with the mobile navigation captured both open and closed. ## Full-view comparison evidence -The source crop shows the prior three-row mobile filter layout: search on its own row, two selects on the next row, and four icons on a third row. The user requested a denser two-row arrangement. +The source shows a permanently visible `48px` navigation rail that reduces the project-content width. The revised closed state removes that rail entirely: browser geometry reports the main region at `543px`, equal to the `543px` iframe viewport. The top-left control is now a menu icon and the project list uses the full available width. -The implementation now uses a wrapping flex layout at container widths up to `460px`. Search and type occupy the first line; status and the four-icon action group occupy the second line. The status select can shrink from `110px` to `90px`, while the icon group keeps its required width and aligns right. - -A local Sites preview started successfully, but the cloud browser rejected `terminal.local` with `net::ERR_BLOCKED_BY_CLIENT`. Therefore no browser-rendered implementation screenshot is available. +The revised open state shows the same navigation as a `543 × 936` fixed full-screen layer. Brand, navigation items and the bottom backend entry remain vertically separated, while an explicit close icon is available in the navigation header. ## Focused region comparison evidence -- Row 1: search flexes into remaining space; type remains `120px` wide. -- Row 2: status starts at `110px` and may shrink to `90px`; icon controls reserve at least `152px` and consume remaining width. -- Spacing: both rows use a shared `10px` gap. -- Overflow protection: status is the only shrinking control on the second row, so the four icon buttons remain complete. -- Accessibility and behavior: filter labels, layout switching, refresh, batch action, and mobile-disabled popovers are unchanged. - -These are code-level checks only and do not replace a rendered focused-region comparison. +A separate crop was not needed because both states are rendered at 1:1 CSS width in the full-view comparison and the header controls, navigation item, content boundary and footer action remain readable. ## Required fidelity surfaces -- Fonts and typography: no typography or truncation rules changed. -- Spacing and layout rhythm: the filter area returns to two compact rows with a consistent `10px` gap. -- Colors and visual tokens: existing dark-theme surface and control tokens are unchanged. -- Image quality and asset fidelity: no image behavior or assets changed. -- Copy and content: all search, type, status, and icon action labels are preserved. +- Fonts and typography: existing font family, sizes, weights and truncation behavior are preserved. The drawer uses the same brand and menu typography as the desktop sidebar. +- Spacing and layout rhythm: the closed state removes the previous `48px` content offset. The open state uses the full viewport and preserves the existing header, menu and footer spacing. +- Colors and visual tokens: the drawer continues to use `--app-subtle`, existing selected-menu colors and the current dark-theme tokens. +- Image quality and asset fidelity: no raster assets were added or altered. Menu and close actions use the project's existing Lucide icon package. +- Copy and content: brand, “我的剧本” and “后端连接” labels are unchanged. ## Findings -- [P1] Browser-rendered comparison unavailable - - Location: 形态图片移动端筛选区。 - - Evidence: the required cloud browser returns `net::ERR_BLOCKED_BY_CLIENT` for the healthy local preview. - - Impact: exact wrapping, control widths, touch spacing, and console state cannot be visually certified. - - Fix: capture the page in an available browser near `360px` content width and compare the two-row filter layout. +- No actionable P0/P1/P2 visual or responsive issues remain. +- The disconnected local backend produces the expected in-page `502` status in the closed-state project list; it does not affect the navigation layout or interaction. ## Primary interactions tested -Automated tests verify the mobile wrapping flex layout, search/type sizing, shrinkable status width, protected icon-group width, filter behavior, icon ordering, layout switching, and refresh workflow. All 275 tests pass. +- Opened the full-screen navigation from the new menu button. +- Closed the navigation from its internal close button. +- Confirmed the hidden navigation is removed from the accessibility tree with `aria-hidden` and `inert`. +- Confirmed the closed-state main content width equals the mobile viewport width. +- Confirmed desktop behavior remains covered by the existing layout component tests. ## Console errors checked -Not checked because the page could not be opened in the cloud browser. +No application-origin console errors were reported. The cloud browser logged only its own extension metadata messages; these are outside the application. ## Comparison history -- Iteration 1: the previous three-row layout avoided crowding but used more vertical space than requested. -- Iteration 2: consolidated the controls into two rows and made only the status select shrinkable; the full test/build suite passed, while browser capture remained blocked. +- Iteration 1: replaced the persistent mobile rail with a fixed full-screen navigation and added the menu/close icon pair. +- Iteration 2: verified the open state at `543 × 936`, then closed it through the internal action and verified the content region expanded to the full `543px` viewport width. ## Implementation checklist -- [x] Keep search and type on the first mobile row. -- [x] Keep status and all four icons on the second mobile row. -- [x] Allow status to shrink before icon controls are affected. -- [x] Preserve existing filter and action behavior. -- [ ] Capture and compare the rendered mobile state in the cloud browser. +- [x] Hide the mobile navigation without reserving horizontal space. +- [x] Open navigation as a full-screen fixed layer. +- [x] Replace the top toggle glyph with a menu icon. +- [x] Provide an internal close control while the top bar is covered. +- [x] Preserve automatic close on mobile route changes, settings open and Escape. +- [x] Preserve desktop collapse/expand behavior. -final result: blocked +final result: passed diff --git a/src/App.vue b/src/App.vue index ee9f7cf..da63e63 100644 --- a/src/App.vue +++ b/src/App.vue @@ -23,25 +23,21 @@ import { FolderOpen, FileText, Layers, - PanelLeftClose, - PanelLeftOpen, - Settings2 + Menu, + Settings2, + X } from '@lucide/vue' import { AppDialog } from './components/ui' import { useTheme } from './composables/useTheme' import ThemeToggle from './components/ui/ThemeToggle.vue' import { projectAccessKey, type ProjectAccess } from './features/projects/access' -/** 窄屏与桌面共用同一侧栏;窄屏展开时主栏右移,宽度保持折叠时的尺寸。 */ +/** 窄屏将侧栏改为全屏覆盖层,关闭后不再占用正文宽度。 */ const NARROW_NAV = '(max-width: 800px)' /** 折叠侧栏宽度,与菜单 collapsed-width 对齐。 */ const SIDER_COLLAPSED_WIDTH = 64 -/** 展开侧栏宽度,窄屏裁切动画以内层保持该宽度。 */ +/** 桌面端展开侧栏宽度。 */ const SIDER_EXPANDED_WIDTH = 208 -/** 窄屏折叠侧栏宽度,给正文多留空间,仍覆盖 44px 点击区。 */ -const SIDER_COLLAPSED_WIDTH_NARROW = 48 -/** 窄屏展开侧栏宽度。 */ -const SIDER_EXPANDED_WIDTH_NARROW = 168 /** 菜单图标尺寸,折叠/展开共用,避免切换时缩放。 */ const NAV_ICON_SIZE = 20 @@ -51,14 +47,14 @@ const narrowMedia = window.matchMedia(NARROW_NAV) const narrow = ref(narrowMedia.matches) const collapsed = ref(narrow.value) const settingsOpen = ref(false) -/** 折叠只裁切文字,不把菜单收成图标,宽屏与窄屏共用同一套宽度动画。 */ +/** 桌面折叠只裁切文字;移动端侧栏打开时始终显示完整菜单。 */ const menuCollapsed = false -/** 当前视口下的折叠侧栏宽度。 */ -const siderCollapsedWidth = computed(() => (narrow.value ? SIDER_COLLAPSED_WIDTH_NARROW : SIDER_COLLAPSED_WIDTH)) -/** 当前视口下的展开侧栏宽度。 */ -const siderExpandedWidth = computed(() => (narrow.value ? SIDER_EXPANDED_WIDTH_NARROW : SIDER_EXPANDED_WIDTH)) +/** Naive Menu 的桌面折叠宽度;移动端覆盖层不参与正文宽度计算。 */ +const siderCollapsedWidth = SIDER_COLLAPSED_WIDTH +/** 侧栏展开宽度仅用于桌面布局。 */ +const siderExpandedWidth = SIDER_EXPANDED_WIDTH /** 折叠与展开共用的图标左偏移,避免切换时图标跳动。 */ -const navIconIndent = computed(() => siderCollapsedWidth.value / 2 - NAV_ICON_SIZE / 2) +const navIconIndent = computed(() => (narrow.value ? 20 : siderCollapsedWidth / 2 - NAV_ICON_SIZE / 2)) /** 视口跨过断点时:进入窄屏收回侧栏。 */ function syncNarrow(event: MediaQueryListEvent) { @@ -93,8 +89,11 @@ function onKeydown(event: KeyboardEvent) { window.addEventListener('keydown', onKeydown) onScopeDispose(() => window.removeEventListener('keydown', onKeydown)) -/** 顶栏切换按钮的可访问名称。 */ -const navToggleLabel = computed(() => (collapsed.value ? '展开侧栏' : '折叠侧栏')) +/** 顶栏菜单按钮在移动端使用更符合抽屉导航语义的可访问名称。 */ +const navToggleLabel = computed(() => { + if (narrow.value) return collapsed.value ? '打开主菜单' : '关闭主菜单' + return collapsed.value ? '展开侧栏' : '折叠侧栏' +}) const { preference, theme, overrides } = useTheme() const projectId = computed(() => String(route.params.projectId || '')) const projectAccess = shallowRef(null) @@ -156,24 +155,40 @@ const menuOptions = computed(() => [ '--admin-sider-expanded': siderExpandedWidth + 'px' }" > - +