Center the login gate so the password field is not a full-width strip.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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<HTMLInputElement>('.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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,16 +27,17 @@ function showSessionGate(): Promise<string> {
|
||||
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<string> {
|
||||
|
||||
const form = el(
|
||||
'form',
|
||||
{ class: 'session-gate__form' },
|
||||
{ class: 'form' },
|
||||
passwordLabel,
|
||||
nameLabel,
|
||||
error,
|
||||
@@ -63,7 +64,8 @@ function showSessionGate(): Promise<string> {
|
||||
void submitStep();
|
||||
});
|
||||
|
||||
root.append(title, form);
|
||||
panel.append(title, form);
|
||||
root.append(panel);
|
||||
app.replaceChildren(root);
|
||||
paint();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user