From 66451bc62950f3a78342b1074dccd6bfac365b0b Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 09:18:48 +0300 Subject: [PATCH 1/2] Center the login gate so the password field is not a full-width strip. Co-authored-by: Cursor --- docs/bugs/01-login-layout.md | 33 +++++++++++++++++++ docs/bugs/README.md | 3 +- src/HSchool.Client/src/style.css | 29 ++++++++++++++++ src/HSchool.Client/src/ui/sessionGate.test.ts | 15 +++++++++ src/HSchool.Client/src/ui/sessionGate.ts | 10 +++--- 5 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 docs/bugs/01-login-layout.md diff --git a/docs/bugs/01-login-layout.md b/docs/bugs/01-login-layout.md new file mode 100644 index 0000000..c537755 --- /dev/null +++ b/docs/bugs/01-login-layout.md @@ -0,0 +1,33 @@ +# Баг 1. Окно входа растянуто на весь экран + +## Симптом + +После фазы 38 экран «Вход» прижат к верхнему левому углу: заголовок и подпись «Пароль альфы» слева сверху, поле пароля — тонкая полоса на всю ширину окна, кнопка «Продолжить» маленькая под левым краем. Большая часть экрана пустая. + +Ожидали компактную форму по центру, как диалог создания школы, а не полноширинный `.screen`. + +## Причина + +Фаза 38 повесила `class="screen session-gate"` и `field__input`, но стилей для `.session-gate` нет, а класс `.field__input` нигде не определён — живой контрол это `.input`. Экран тянется на 100% ширины (`#app`), нативное поле без паддинга заполняет окно, колонка стартует с верхнего края (у `.screen.menu` как раз есть `justify-content: center` и `max-width`). + +## Путь + +Стили `.session-gate`: карточка размера диалога по центру окна; поля перевести на `.input`. Не делать `` — за формой ничего нет, это единственный экран, не модалка поверх меню. + +## Задачи + +- [x] Карточка входа по центру, ширина как у `.dialog` (~340–360px), не на всю `#app` +- [x] Поля пароля и имени — класс `input` +- [x] Тест на классы гейта и `input` + +## Тест, без которого не закрыт + +Клиентский Vitest: без куки монтируется `.session-gate` с панелью, поле пароля несёт `input`, не `field__input`. + +```bash +npm --prefix src/HSchool.Client test -- src/ui/sessionGate.test.ts +``` + +## Стоп + +Протокол, куку, шаги пароль/имя и HTTP сессии не трогать. Локаль на гейте не подписывать — это соседнее. diff --git a/docs/bugs/README.md b/docs/bugs/README.md index f51d2b3..11ac637 100644 --- a/docs/bugs/README.md +++ b/docs/bugs/README.md @@ -9,5 +9,4 @@ | Баг | Статус | Симптом | | --- | --- | --- | - -Пока пусто. Первую строку пишет `/bug-work`. +| [1. Окно входа](01-login-layout.md) | 🔄 | Форма входа прижата к углу, поле пароля на всю ширину | diff --git a/src/HSchool.Client/src/style.css b/src/HSchool.Client/src/style.css index c94e927..8397ef5 100644 --- a/src/HSchool.Client/src/style.css +++ b/src/HSchool.Client/src/style.css @@ -63,6 +63,35 @@ body { justify-content: center; } +/* + * Phase 38 dropped the gate in as a full-width .screen and never gave it a column. Without a cap + * the password field spans the window; without centering it clings to the top-left. Same tokens as + * .dialog — this is one short form, not a management screen. + */ +.screen.session-gate { + align-items: center; + justify-content: center; +} + +.session-gate__panel { + display: flex; + flex-direction: column; + gap: 16px; + width: min(360px, 100%); + padding: 24px; + border: 1px solid var(--border); + border-radius: 14px; + background: var(--surface-raised); +} + +.session-gate .form { + width: 100%; +} + +.session-gate .button { + align-self: flex-start; +} + /* * No cap. A reading column has a sensible maximum width; a management screen does not — it is a * tree, a table and a timetable grid, and every pixel taken away from them is a pixel of scrolling. diff --git a/src/HSchool.Client/src/ui/sessionGate.test.ts b/src/HSchool.Client/src/ui/sessionGate.test.ts index 365c473..d37faa5 100644 --- a/src/HSchool.Client/src/ui/sessionGate.test.ts +++ b/src/HSchool.Client/src/ui/sessionGate.test.ts @@ -37,4 +37,19 @@ describe('session gate', () => { pending.catch(() => undefined); }); + + it('mounts a centred panel and the shared input class, not a raw full-width field', async () => { + vi.mocked(fetchSession).mockRejectedValue(new ApiError(401, 'unknown', 'Unauthorized')); + + const pending = ensureSession(); + await Promise.resolve(); + + const gate = document.querySelector('.session-gate'); + const password = document.querySelector('.session-gate input[type="password"]'); + expect(gate?.querySelector('.session-gate__panel')).not.toBeNull(); + expect(password?.className).toBe('input'); + expect(password?.className).not.toContain('field__input'); + + pending.catch(() => undefined); + }); }); diff --git a/src/HSchool.Client/src/ui/sessionGate.ts b/src/HSchool.Client/src/ui/sessionGate.ts index 2ac39d4..79f3877 100644 --- a/src/HSchool.Client/src/ui/sessionGate.ts +++ b/src/HSchool.Client/src/ui/sessionGate.ts @@ -27,16 +27,17 @@ function showSessionGate(): Promise { return new Promise((resolve) => { let password = ''; const root = el('section', { class: 'screen session-gate' }); + const panel = el('div', { class: 'session-gate__panel' }); const title = el('h1', { class: 'screen__title' }); const passwordLabel = el('label', { class: 'field' }); const passwordInput = el('input', { - class: 'field__input', + class: 'input', type: 'password', autocomplete: 'current-password', }) as HTMLInputElement; const nameLabel = el('label', { class: 'field' }); const nameInput = el('input', { - class: 'field__input', + class: 'input', type: 'text', autocomplete: 'username', maxlength: '40', @@ -51,7 +52,7 @@ function showSessionGate(): Promise { const form = el( 'form', - { class: 'session-gate__form' }, + { class: 'form' }, passwordLabel, nameLabel, error, @@ -63,7 +64,8 @@ function showSessionGate(): Promise { void submitStep(); }); - root.append(title, form); + panel.append(title, form); + root.append(panel); app.replaceChildren(root); paint(); From 6eb32e732b2a12e14da6fb9eb06df2f5cbcc0587 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 09:19:34 +0300 Subject: [PATCH 2/2] bug 1: mark complete Co-authored-by: Cursor --- docs/bugs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bugs/README.md b/docs/bugs/README.md index 11ac637..38a552c 100644 --- a/docs/bugs/README.md +++ b/docs/bugs/README.md @@ -9,4 +9,4 @@ | Баг | Статус | Симптом | | --- | --- | --- | -| [1. Окно входа](01-login-layout.md) | 🔄 | Форма входа прижата к углу, поле пароля на всю ширину | +| [1. Окно входа](01-login-layout.md) | ✅ | Форма входа прижата к углу, поле пароля на всю ширину |