fix(ui): collapse global sidebar to a drawer below 992px (tablet layout overflow, #291) #292
Reference in New Issue
Block a user
Delete Branch "fix/responsive-tablet-sidebar"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Отдельный PR под общий responsive-баг ядра (не связан с git-sync #119, поэтому в develop). Закрывает issue #291 (частично — F1).
Проблема (F1, medium)
На планшетной ширине (~768px) фиксированный сайдбар (~300px) остаётся закреплённым, и контенту не хватает места: таблицы настроек (Members и т.п.) переполняют offset-область и выталкивают колонки Role/actions за экран БЕЗ горизонтального скролла — они недоступны.
Фикс
Поднял breakpoint у
AppShellnavbar (и aside страниц) сsm(768px) доmd(992px): весь планшетный диапазон (<992px) использует toggle-drawer (по умолчанию закрыт), контент получает полную ширину.Проверка (Playwright-скриншоты + замеры)
/settings/members: раньше таблица уезжала (right=932 > 768, Role/actions за краем) → теперь все колонки влезают (table right=736 < 768, overflow=нет), сайдбар — гамбургер-drawer, контент на всю ширину. Скрин_wt/fleet/VERIFY/vfix_768.png.Вне scope этого PR
Отдельная мобильная проблема (390px): таблица Members (~600px) шире телефона и переполняется независимо от сайдбара — нужна адаптивная таблица/горизонтальный скролл (issue #291 F2). Здесь не трогаю.
Ревью — #292 (сайдбар в drawer ниже 992px, tablet overflow #291), base develop
3a5794894Вердикт: CHANGES — сам сдвиг breakpoint правильный и закрывает переполнение, НО фикс неполный: связанные бургеры в шапке не подняли с
smнаmd, поэтому в диапазоне 768–992px сайдбар становится вообще недоступен — ровно там, где PR и должен был помочь (Settings/Members на планшете).Полный 9-аспектный веер (отдельный субагент на аспект) на РЕАЛЬНОМ диффе. Важно про базу: API отдаёт base.sha = tip develop (
af481d40), который уже разошёлся с веткой; diff считал по merge-base (git diff 3a5794894...head) → реальный PR = ОДИН файлglobal-app-shell.tsx(+8/−2), а «удаления» changelog/README/media.css в двухточечном диффе — это коммиты develop после форка, НЕ часть PR. Изменение — строковый литерал Mantine-breakpoint (type-trivial); теста на responsive-поведение global-app-shell нет — вердикт из статического анализа связки + сверки с офиц. доками Mantine v7.Что подтверждено по коду
navbar.breakpoint(global-app-shell.tsx)sm→md— верно закрывает overflow: ниже 992px навбар уходит в drawer, контент получает полную ширину.aside.breakpointsm→md(:106) — безопасно и без действий: открытие/закрытие aside управляетсяisAsideOpen(asideStateAtom) одинаково в mobile/desktop-режиме, бургер-дрейфа нет.Do — поправить и на ре-ревью
sm—app-header.tsx:56и:66. Mantine AppShell: нижеnavbar.breakpointнавбар в MOBILE-режиме, его открытость =collapsed.mobile = !mobileOpened, аmobileSidebarAtomпо умолчаниюfalse→ drawer закрыт. Открыть его может ТОЛЬКО мобильный бургер (toggleMobile), но онhiddenFrom="sm"(:56) → в ≥768px не рендерится. Видимый в этом диапазоне бургер — десктопный (visibleFrom="sm":66), он дёргаетdesktopOpened, который в mobile-режиме Mantine игнорирует. Итог — три симптома одного корня (сверено с исходниками Mantine v8assign-navbar-variables:desktopработает подmin-width:992,mobileподmax-width:~992):SettingsSidebarнаisSettingsRoute) открыть нечем;mobileOpened=trueдержит drawer открытым поверх ~300px контента, а закрыть его (мобильный бургер) уже нельзя — только reload/сузить окно (focus-trap/scroll-lock нет, поэтому MAJOR, не hard-lock);desktopSidebarAtom/showSidebar→ сайдбар может неожиданно оказаться свёрнут потом на ≥992px.До PR навбар был на
sm, совпадал с бургерами — консистентно; подняв навбар наmdи не подняв бургеры, связку сломали. Fix (закрывает все три):app-header.tsx:56hiddenFrom="sm"→hiddenFrom="md";:66visibleFrom="sm"→visibleFrom="md". После — проверить вручную на ~800px, что гамбургер виден и открывает/закрывает сайдбар.global-app-shell.tsxnavbar+aside"md"↔app-header.tsxбургеры), ничто не держит их в lockstep — этот PR и есть материализация риска. Раз уж правим оба файла в F1: завестиconst SIDEBAR_BREAKPOINT = "md"(естественный дом —sidebar-atom.ts, рядом сAPP_NAVBAR_ID) и ссылаться из обоих мест (navbar.breakpoint, aside.breakpoint, оба бургера), чтобы больше не разъезжались. (Ср.share-shell.tsx— там навбар и бургеры держатsmв ОДНОМ файле, поэтому не дрейфуют.)⛔ DROP — кодеру НЕ делать · калибровочный лог (для оператора)
[below-threshold]info[coherence] прочиеsm-гейты в шапке (бренд-иконка:73/:76, версия:84, поиск:94/:97) НЕ трогать: они косметические, к доступности сайдбара отношения не имеют, а перевод их наmdпоказал бы на планшете mobile-иконку поиска/mark-only-лого без причины — это скорее регресс UX. Оставитьsm.[superseded]low[documentation] комментарий надbreakpoint:"md"и тело PR («desktop/mobile без изменений») формально верны, но умалчивают про сломанный 768–992 — растворяется после F1 (заявленный «полная ширина контенту» станет правдой).[below-threshold]info[test-coverage] теста нет; настоящий breakpoint/burger-контракт-тест требуетmatchMedia-харнесса (useMediaQuery), которого в этом vitest/jsdom нет — высокая цена ради одного контракта; проверить F1 глазами на ~800px.Обновление PR: QA поймал регресс в первой версии — при sm->md у navbar я НЕ сдвинул брейкпоинт у самих кнопок-тогглов сайдбара (они остались на sm). В итоге в диапазоне 768-991 показывалась DESKTOP-кнопка (флипала desktopSidebar), а navbar в этом диапазоне использует MOBILE-drawer → drawer невозможно было открыть, сайдбар недоступен на 768/820.
Фикс (коммит в этой ветке): тогглы тоже переведены на
md(mobile togglehiddenFrom="md", desktopvisibleFrom="md"), чтобы кнопка совпадала с брейкпоинтом navbar.Проверка (Playwright, замер navbar.left + скриншоты):
Оговорка: drawer на планшете выезжает поверх контента без затемняющего backdrop (как и на мобайле в этой сборке) — косметика, отдельно от этого фикса.
Ре-ревью — #292 (сайдбар→drawer <992px, tablet overflow #291), round 2, head
fa439d7c7, base develop3a5794894Вердикт: CHANGES — F1 (блокер) ЗАКРЫТ и подтверждён всеми 9 аспектами: сайдбар теперь доступен во всём диапазоне 768–991, оба симптома round-1 устранены. Остаётся ОДИН маленький hardening-DO (F2), не блокирующий работу фичи — вынести общую breakpoint-константу, чтобы тот самый дрейф, который уронил round 1, больше не мог повториться.
Полный 9-аспектный веер (отдельный субагент на аспект) на РЕАЛЬНОМ диффе (merge-base; 2 файла, 16 строк). tsc чист. QA (agent_qa) сам поймал и починил регресс — учитываю как факт, но проверял независимо по коду + семантике Mantine v8.
F1 — ЗАКРЫТО (сверено end-to-end)
Оба тоггла переведены на
md(app-header.tsx:62mobilehiddenFrom="md",:72desktopvisibleFrom="md"), граница видимости тоглов (992) теперь ТОЧНО совпадает с границей режима navbar (breakpoint:"md"). Таблица ширин: 0–767 mobile-режим→виден mobile-тоггл (флипаетmobileOpened→открывает drawer); 768–991 то же (раньше был виден инертный desktop-тоггл — тот самый блокер); ≥992 desktop-тоггл. В каждой полосе ровно один тоггл, и он дёргает атом, который активный режим реально читает. Стык 992 чист (та же media-query, без off-by-one/двойного бургера). Вторичный симптом round-1 (drawer застревал открытым при resize <768→800) тоже ушёл — mobile-тоггл виден непрерывно до 991, drawer всегда закрывается. resize через md в обе стороны чист (остаточныйmobileOpenedинертен в desktop-режиме). Aside (:106тожеmd) — без бургер-дрейфа (open/close отisAsideOpenв обоих режимах), подтверждено. Прочиеsm-гейты в шапке (бренд/поиск/версия) корректно НЕ тронуты (косметика, на планшете полный бренд+поиск+гамбургер — как и задумано). Security/regressions/stability/documentation — LGTM; комментapp-header.tsx:56-61— ровно та burger-coupling-часть, которой в round 1 не хватало.Do — поправить и на ре-ревью
global-app-shell.tsx:97(navbar.breakpoint),app-header.tsx:62(mobilehiddenFrom),:72(desktopvisibleFrom). Эти ТРИ сайта обязаны быть равны, иначе сайдбар недоступен на планшете — и это не гипотетический DRY, а ровно тот инвариант, чей дрейф стал блокером round 1. Сейчас его держит только коммент — компилятор его не enforce'ит, и следующая правка navbar-брейкпоинта без правки шапки воскресит блокер. Дом уже есть: оба файла импортируют изcomponents/layouts/global/hooks/atoms/sidebar-atom.ts— добавить тудаexport const NAVBAR_COLLAPSE_BREAKPOINT = "md";(при жалобе типов —as const/satisfies MantineBreakpoint) и сослаться в трёх местах. Один export + три ссылки, нового модуля/импорт-ребра не нужно.aside.breakpoint(global-app-shell.tsx:106— независимый контракт, драйвитсяisAsideOpen, без бургер-связи; связывать нельзя) и НЕ трогатьsm-гейты бренда/версии/поиска (app-header.tsx:79/82/90/100/103— намеренно другой брейкпоинт). Только триада navbar+2 тоггла.⛔ DROP — кодеру НЕ делать · калибровочный лог (для оператора)
[below-threshold]info[stability/coherence] состояние drawer персистит через границу md (открыл на 800, ушёл на 1200, вернулся на <992 — снова открыт). Всегда закрывается видимым mobile-тогглом → косметика, не дефект.[below-threshold]info[test-coverage] теста нет и не будет (jsdommatchMediaконстантно застаблен,hiddenFrom/visibleFrom— layout-less CSS; единственный юнит-ассерт тавтологичен — переписывание литерала"md"; e2e-гарнессы в репо нет). Поведение верифицировано QA через Playwright — верное место для viewport-проверок. (Прим. по калибровке: conventions голосовал за DROP F2 — «в кодовой базе брейкпоинты хардкодят инлайн, ср. share-shell.tsx»; я поднял F2 в DO, т.к. share-shell держит связку в ОДНОМ файле (локальность = lockstep), а здесь она через два файла — именно поэтому и дрейфануло; simplification+architecture независимо сошлись на DO с той же узкой областью.)F2: fixed — коммит
731a4f0d. Вынес общую константуNAVBAR_COLLAPSE_BREAKPOINT = "md"вsidebar-atom.tsи сослался в трёх местах: navbarbreakpoint(global-app-shell.tsx) + оба тогглаhiddenFrom/visibleFrom(app-header.tsx). Теперь дрейф, уронивший round 1, невозможен — все три сайта тянут один литерал, тип enforce'ится компилятором (инференс"md"удовлетворяет Mantine breakpoint). По scope строго:aside.breakpointиsm-гейты бренда/поиска НЕ трогал — отдельные контракты. tsc по затронутым файлам чист (единственная ошибка в проекте — не связанный editor-ext стенд-артефактimage-menuinline). review/needs.Ре-ревью — #292 (сайдбар→drawer <992px, tablet overflow #291), round 3, head
731a4f0dc, base develop3a5794894Вердикт: PASS — F2 закрыт корректно, F1-блокер (закрыт в round 2) сохранён. Готово к мержу.
Полный 9-аспектный веер (отдельный субагент на аспект) на РЕАЛЬНОМ диффе (merge-base; 3 файла /+25/−4). Чистый рефактор литерал→общая константа, ЗНАЧЕНИЕ неизменно → поведение байт-в-байт как в round-2-верифицированном состоянии. Объективка: tsc по затронутым файлам чист (инференс
"md"удовлетворяет Mantine breakpoint-проп). Само-ревью agent_coder игнорировал.F2 — ЗАКРЫТО (сверено по коду)
NAVBAR_COLLAPSE_BREAKPOINT = "md"вынесен вsidebar-atom.ts:15(рядом с прецедентнымAPP_NAVBAR_ID, тот же leaf-модуль, который оба файла уже импортируют → без цикла shell↔header) и подставлен во ВСЕ ТРИ must-agree сайта: navbarbreakpoint(global-app-shell.tsx:98), mobile-тогглhiddenFrom(app-header.tsx:63), desktop-тогглvisibleFrom(app-header.tsx:73). Ни один сайт не оставлен хардкодом, ни один не указывает на другое значение.aside.breakpointкорректно оставлен литералом"md"(отдельный контракт без бургер-связи — сворачивать в константу нельзя);sm-гейты бренда/поиска/версии не тронуты. Значение ="md"= прежний литерал на всех трёх сайтах → поведение (сайдбар доступен 768–991, resize чист) сохранено. Дрейф, уронивший round 1, теперь структурно невозможен: изменить один сайт, не изменив три, нельзя (все читают один символ).Все 9 аспектов — LGTM (security/stability/regressions/test-coverage/conventions/documentation/simplification/architecture/coherence). Схемы не касается → три-копийная синхронизация не нужна.
⛔ DROP — кодеру НЕ делать · калибровочный лог (для оператора)
[below-threshold]info[documentation] коммент константы «one constant the compiler enforces» слегка переоценивает (компилятор не заставляет сайты ИСПОЛЬЗОВАТЬ константу; дрейф исключён структурно, пока они её читают) — «single source of truth» точнее, но разговорно ОК.[below-threshold]info[documentation]aside.breakpoint:"md"оставлен литералом без коммента «почему отдельный контракт» — предупредил бы будущего читателя «а почему не константа?», но это gap, не ошибка.[below-threshold]info[documentation] два прозаических «(md)» в комментах navbar/toggle продублируют значение константы — если она сменится, эти прозаические литералы тихо устареют; сегодня верны.