Repository navigation
Commit 6ab6b72
fix(app-shell): the narrow-window sidebar rail draws no expanded first frame, folds on one Esc, and keeps the wide-window choice apart (#28141)
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgDFH4S5pR1C8YvYCWWsZfXx)_
## 维护者速读
- 改了什么:窄窗(低于 1416 px)下控制台侧栏的三处返工。① 首帧就是 48 px 图标轨,不再先画 256 px 再收;②
浮出的侧栏里,鼠标停在条目上或焦点在条目/「保持侧栏展开」上,按一次 Esc 就收起;③
宽窗的展开/收起选择单独保存,窄窗里开关「保持侧栏展开」不再改它,刷新后也一样。
- 为什么改:PR #27893 合并后的代理预览审查(评论 6521834471)在 app-crm 上实测到这三处:首帧闪、第一次 Esc
被隐藏的条目提示吃掉、宽窗选择被窄窗开关覆盖。
- 风险与回滚:只动 app-shell
控制台外壳(`consoleSidebarRail.ts`、`useResponsiveSidebar.ts`、`ConsoleLayout.tsx`
一行)和测试;阈值 1416 不变;`components/src/ui/**` 没碰,无新 i18n 键、无发布包新导出。宽窗选择沿用原来的
`os:console-sidebar:wide-open` 键,老数据语义一致。回滚即还原本 PR。
-
先看:`ui/packages/app-shell/src/layout/consoleSidebarRail.ts`(`primeConsoleSidebarOpen`
首帧、`escapeOnlyForHiddenTooltip` Esc
判定、宽窗选择的读写);`ui/packages/app-shell/src/hooks/useResponsiveSidebar.ts`(只有宽窗的开合写宽窗选择)。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/layout/__tests__/ConsoleLayout.sidebarFirstFrame-28077.test.tsx
packages/app-shell/src/layout/__tests__/UnifiedSidebar.railOverlay-27593.test.tsx`,预期全绿;或
1280 px 打开 app-crm 联系人,点轨道空白处浮出,鼠标停在「Accounts」上按一次 Esc,侧栏收回。
- 需要你做的:无,席位落地。
Fixes #28077
Fixes #28078
Fixes #28079
Clause-②: no
Metadata-protocol: none
## What changes
- **#28077, the first frame is the rail.** `SidebarProvider` takes its
first open state from the `sidebar_state` cookie the last page wrote, so
a narrow window drew the expanded sidebar once and the fold then closed
it, with the primitive's 200 ms width transition in between.
`ConsoleLayout` now calls `primeConsoleSidebarOpen()` once (a `useState`
initializer) before `AppShell`'s provider renders: below the fold width
with no "keep open" it sets the cookie to `false`, on a wide window to
the reader's wide-window choice; a sidebar kept open and the phone keep
what the cookie says. The viewport hook was already synchronous
(`useIsViewportBelow` reads `innerWidth` in its initializer), so the
flash came from the provider's cookie, not from the hook.
`useConsoleSidebarRail` also reads the phone breakpoint synchronously,
so a phone-width first render is never taken for a narrow one while
`useIsMobile` still answers `false`.
- **#28078, one Esc folds the overlay.** While the sidebar is expanded
the primitive keeps an entry's Radix tooltip open but `hidden` (hover or
focus opens it); its dismissable layer takes the next Esc on the
document's capture phase and calls `preventDefault`, so the overlay's "a
layer handled it" check skipped the fold. The overlay now also listens
on the window's capture phase, ahead of every layer: when an open
tooltip is hidden and no dialog, alert dialog, menu or listbox is shown,
that Esc folds the overlay even though it arrives marked handled. An
entry's open menu still takes the Esc first (pinned). Fixed from the
sidebar side; `components/src/ui/**` untouched.
- **#28079, the wide-window choice is kept apart.**
`os:console-sidebar:wide-open` was a stash written when the rail took
over and cleared when keep-open turned on, so keep-open on and then off
re-stashed the narrow overlay's state. It is now the reader's
wide-window choice: written on every open and close while the window is
at or above the fold width, and only then; handed back when the window
turns wide and on a wide load. The fold, the overlay and a sidebar kept
open on a narrow window never write it; `console.sidebar.keepOpen` stays
under its own key. When no wide-window choice is kept yet (first visit
on a narrow window), the open state the fold first closes seeds it.
## Tests
- New `layout/__tests__/ConsoleLayout.sidebarFirstFrame-28077.test.tsx`
(5): the real `ConsoleLayout` / `AppShell` / `SidebarProvider`, a probe
in the sidebar's place recording the state of every render. At 1280 px a
first visit, a load after an expanded wide window, and a reload with the
cookie expanded: every render, the first included, is `collapsed`. 1440:
every render `expanded`. Keep-open at 1280 keeps the cookie's state.
- `layout/__tests__/UnifiedSidebar.railOverlay-27593.test.tsx` (+6): one
Esc folds with focus on an entry (its hidden tooltip open), with the
pointer on an entry, with focus on Keep sidebar open; an entry's open
menu still takes the Esc; the #28079 sequence in both directions
(collapsed and expanded wide-window choice), widened and then reloaded.
- `hooks/__tests__/useResponsiveSidebar.test.tsx`: follows the kept
wide-window choice (it is no longer cleared when the window widens).
- `chrome/KeyboardShortcutsDialog.wiredOnly-11674.test.tsx`: its probe
already cleared the sidebar cookie between mounts; it now also clears
the wide-window key, since the ⌘ run's toggle is kept as the wide-window
choice the Ctrl run would otherwise start from.
- Ablations (each mutation through `scripts/ablation-replace.mjs`,
landed and restored by blob, `git diff HEAD` empty after each): drop the
`primeConsoleSidebarOpen` call: the three 1280 first-frame tests red,
1440 and keep-open green. Answer `false` for the hidden-tooltip check:
the three one-Esc tests red. Let the narrow keep-open sidebar write the
wide-window choice: the expanded-direction #28079 test red. Let the fold
write (not seed) the wide-window choice: both #28079 tests red.
## Acceptance notes
- **Scope:** one file outside the five named:
`chrome/KeyboardShortcutsDialog.wiredOnly-11674.test.tsx`, a test that
mounts the real `ConsoleLayout`; the change is its between-mounts
cleanup, one line.
- The hidden-tooltip check reads the DOM (a `role=tooltip` inside a
`hidden` element; a shown `dialog` / `alertdialog` / `menu` /
`listbox`). A layer drawn with none of those roles that marks Esc
handled while a hidden tooltip is open would see the overlay fold too;
none is drawn in the console shell today.
- The threshold (1416 px, `CONSOLE_SIDEBAR_RAIL_BELOW_WIDTH`) is
unchanged, as ruled.
## Verification
Head `55d0fb34`.
- `pnpm --dir ui exec vitest run` of the 103 app-shell test files that
reference `ConsoleLayout`, `UnifiedSidebar`, `useResponsiveSidebar`,
`consoleSidebarRail`, `SidebarProvider` or `sidebar_state`: Test Files
103 passed, Tests 1009 passed.
- `pnpm --filter @object-ui/app-shell lint`: 0 errors.
- Declared to CI: the full `@object-ui/app-shell` test (1967 files;
stopped locally after an hour on the shared box) and `type-check` (needs
every `@object-ui/*` package built).
- Real browser (Chromium, `examples/app-crm` with `objectstack dev
--seed-admin --fresh`, the console on its Vite dev server, Contacts): at
1280 px every sampled animation frame from the first one is the 48 px
rail on a first visit, a load after an expanded 1440 px window, and a
reload with the overlay shown; at 1440 px every frame is 256 px. In the
overlay one Esc folds it with the pointer on Accounts (its tooltip open
and hidden), with focus on the Accounts link, and with focus on Keep
sidebar open. Collapse at 1440, 1280, keep open on and off, 1440:
collapsed, and after a reload; the expanded direction comes back
expanded, and after a reload.
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 2c683cf commit 6ab6b72
8 files changed
Lines changed: 397 additions & 49 deletions
File tree
- .changeset
- ui/packages/app-shell/src
- chrome
- hooks
- __tests__
- layout
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
149 | | - | |
| 149 | + | |
| 150 | + | |
150 | 151 | | |
151 | 152 | | |
| 153 | + | |
152 | 154 | | |
153 | 155 | | |
154 | 156 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
8 | 10 | | |
9 | 11 | | |
10 | 12 | | |
| |||
14 | 16 | | |
15 | 17 | | |
16 | 18 | | |
17 | | - | |
| 19 | + | |
18 | 20 | | |
19 | 21 | | |
20 | 22 | | |
| |||
100 | 102 | | |
101 | 103 | | |
102 | 104 | | |
103 | | - | |
| 105 | + | |
104 | 106 | | |
105 | 107 | | |
106 | 108 | | |
107 | | - | |
| 109 | + | |
108 | 110 | | |
109 | 111 | | |
110 | 112 | | |
| |||
127 | 129 | | |
128 | 130 | | |
129 | 131 | | |
130 | | - | |
| 132 | + | |
131 | 133 | | |
132 | 134 | | |
133 | | - | |
| 135 | + | |
134 | 136 | | |
135 | 137 | | |
136 | 138 | | |
137 | 139 | | |
138 | 140 | | |
139 | 141 | | |
140 | | - | |
| 142 | + | |
141 | 143 | | |
142 | 144 | | |
143 | 145 | | |
144 | 146 | | |
145 | 147 | | |
146 | | - | |
| 148 | + | |
147 | 149 | | |
148 | 150 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
11 | | - | |
12 | | - | |
13 | | - | |
14 | | - | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
| 22 | + | |
22 | 23 | | |
23 | | - | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
28 | | - | |
29 | | - | |
| 29 | + | |
30 | 30 | | |
| 31 | + | |
31 | 32 | | |
32 | 33 | | |
33 | | - | |
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
43 | 51 | | |
44 | 52 | | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
52 | 59 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| |||
135 | 136 | | |
136 | 137 | | |
137 | 138 | | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
138 | 142 | | |
139 | 143 | | |
140 | 144 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
0 commit comments