diff --git a/frontend/src/components/AnalysisShell.css b/frontend/src/components/AnalysisShell.css index 4d1f955..2d7eaec 100644 --- a/frontend/src/components/AnalysisShell.css +++ b/frontend/src/components/AnalysisShell.css @@ -16,7 +16,10 @@ margin-inline: calc(50% - 50vw); padding-inline: max(16px, calc(50vw - 660px)); } -.ax-layout.is-collapsed { grid-template-columns: 0 minmax(0, 1fr); gap: 0; } +/* One column when the rail is away. It was `0 minmax(0, 1fr)`, and since the + rail is `display: none` the content became the *first* grid item — landing + in the zero-width column and wrapping one word per line. */ +.ax-layout.is-collapsed { grid-template-columns: minmax(0, 1fr); gap: 0; } .ax-main { min-width: 0; padding: 8px 0 48px; } diff --git a/frontend/src/components/ArticleSplitPane.css b/frontend/src/components/ArticleSplitPane.css index 78500d3..585a2fa 100644 --- a/frontend/src/components/ArticleSplitPane.css +++ b/frontend/src/components/ArticleSplitPane.css @@ -15,11 +15,11 @@ cross-reference must not move the article you were reading. */ .article-split.is-open > .article-split-main, .article-split.is-open > .article-split-pane { - max-height: calc(100vh - 120px); + max-height: calc(100dvh - 120px); overflow-y: auto; } /* The rail is sticky inside its own pane now, not inside the window. */ -.article-page.is-split .article-sections { top: 6px; max-height: calc(100vh - 200px); } +.article-page.is-split .article-sections { top: 6px; max-height: calc(100dvh - 200px); } .article-page.is-split .article-layout { grid-template-columns: 190px 1fr; gap: 14px; } .article-page.is-split .article-content { padding: 16px 18px; } diff --git a/frontend/src/components/TeachChat.jsx b/frontend/src/components/TeachChat.jsx index 98e7daa..6941503 100644 --- a/frontend/src/components/TeachChat.jsx +++ b/frontend/src/components/TeachChat.jsx @@ -304,7 +304,7 @@ export default function TeachChat({ question, attemptId, elevated = false }) { left: auto !important; top: 0 !important; width: 340px !important; - height: 100vh !important; + height: 100dvh !important; border-radius: 0 !important; border-top: none !important; border-left: 2px solid var(--primary) !important; diff --git a/frontend/src/index.css b/frontend/src/index.css index c65e3ac..1dc30f2 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -114,7 +114,18 @@ body { too wide then scrolls the whole page sideways — which is why the navbar could be found clipped mid-word at its left edge. Anything genuinely wider than the window scrolls inside its own box instead. */ -html, body { overflow-x: hidden; max-width: 100%; } +html, body { max-width: 100%; } +/* `overflow-x: hidden` on an ancestor silently disables `position: sticky` for + every descendant — it makes the element a scroll container, and a sticky + child sticks to that instead of to the viewport. That is why the session + rail, the settings nav and the article column would not stay put. `clip` + does the same job without becoming a scroll container. Hidden is kept as + the fallback, so a browser without `clip` still cannot be scrolled + sideways; it just loses stickiness, which is the lesser fault. */ +html, body { overflow-x: hidden; } +@supports (overflow-x: clip) { + html, body { overflow-x: clip; } +} /* ── Layout ─────────────────────────────────────────────────── */ .container { max-width: 1200px; margin: 0 auto; padding: 0 28px; } @@ -732,7 +743,7 @@ html, body { overflow-x: hidden; max-width: 100%; } border-radius: var(--card-radius); padding: 16px; box-shadow: var(--card-shadow); - max-height: calc(100vh - 100px); + max-height: calc(100dvh - 100px); overflow-y: auto; } diff --git a/frontend/src/pages/AiModePage.css b/frontend/src/pages/AiModePage.css index d28e37c..5aa0d00 100644 --- a/frontend/src/pages/AiModePage.css +++ b/frontend/src/pages/AiModePage.css @@ -3,7 +3,7 @@ .ai-page { display: grid; grid-template-columns: 230px 1fr; gap: 18px; align-items: start; max-width: 1060px; margin: 0 auto; } .ai-rail { - position: sticky; top: 76px; max-height: calc(100vh - 100px); overflow-y: auto; + position: sticky; top: 76px; max-height: calc(100dvh - 100px); overflow-y: auto; background: var(--card-bg); border: 1px solid var(--border); border-radius: 12px; padding: 12px; } @@ -72,7 +72,12 @@ .ai-error { color: var(--wrong-fg); font-size: 0.85rem; margin: 0; } -.ai-composer { display: flex; gap: 8px; align-items: flex-end; position: sticky; bottom: 0; padding-bottom: 12px; background: var(--bg); } +.ai-composer { + display: flex; gap: 8px; align-items: flex-end; + position: sticky; bottom: 0; background: var(--bg); + /* Clear of the home indicator, or the send button sits under it. */ + padding-bottom: calc(12px + env(safe-area-inset-bottom)); +} .ai-composer textarea { flex: 1; min-width: 0; resize: vertical; padding: 11px 14px; border: 1px solid var(--border); border-radius: 10px; diff --git a/frontend/src/pages/AnalysisSessionPage.test.jsx b/frontend/src/pages/AnalysisSessionPage.test.jsx index 8252d30..8742fed 100644 --- a/frontend/src/pages/AnalysisSessionPage.test.jsx +++ b/frontend/src/pages/AnalysisSessionPage.test.jsx @@ -138,6 +138,21 @@ describe('a session whose only attempt is still in progress', () => { }) }) +describe('the session rail', () => { + it('gives the content the whole width when the rail is put away', async () => { + mountAttempt() + await screen.findByRole('complementary', { name: 'Sessions' }) + const layout = document.querySelector('.ax-layout') + expect(layout).not.toHaveClass('is-collapsed') + + await userEvent.click(screen.getByRole('button', { name: /Hide|Collapse/i })) + // Hiding the rail used to leave the content in a zero-width grid column, + // wrapping one word per line beside an empty page. + expect(document.querySelector('.ax-layout')).toHaveClass('is-collapsed') + expect(document.querySelector('.ax-main')).toBeInTheDocument() + }) +}) + describe('a study session with every question answered', () => { it('becomes the review without waiting to be handed in', async () => { api.get.mockImplementation(url => { diff --git a/frontend/src/pages/ArticlesPage.css b/frontend/src/pages/ArticlesPage.css index 11ee83e..b794aa8 100644 --- a/frontend/src/pages/ArticlesPage.css +++ b/frontend/src/pages/ArticlesPage.css @@ -20,7 +20,7 @@ its whole length, scrolling on their own when there are more sections than screen. */ .article-sections { - position: sticky; top: 76px; max-height: calc(100vh - 96px); overflow-y: auto; + position: sticky; top: 76px; max-height: calc(100dvh - 96px); overflow-y: auto; background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--card-radius); padding: 14px; } diff --git a/frontend/src/pages/ForgotPasswordPage.jsx b/frontend/src/pages/ForgotPasswordPage.jsx index 6ec80d5..47a4de7 100644 --- a/frontend/src/pages/ForgotPasswordPage.jsx +++ b/frontend/src/pages/ForgotPasswordPage.jsx @@ -30,7 +30,7 @@ export default function ForgotPasswordPage() { } return ( -
Verifying your email...
>