From f0e42e2ad530b285b48f91ce23059edc8dc69401 Mon Sep 17 00:00:00 2001 From: Austin Schaefer Date: Thu, 3 Sep 2026 19:17:57 +0200 Subject: [PATCH] Fix article list not scrolling: duplicate
broke the scroll area MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Articles past the first screenful were unreachable — not clipped by accident, genuinely inaccessible, since the page itself couldn't scroll either (the sidebar wrapper is overflow: hidden by design, so the intended scroll boundary is internal to the main content pane). Root cause: SidebarInset already renders a
(class dx-sidebar-inset, a properly height-bound flex column — main{ height:900px in a 900px viewport, flex-direction:column}), but I'd also written an explicit `main { ... }` as ITS child, producing a `
...
` (confirmed via a headless Playwright probe against the live dev server, not just DevTools guesswork). The inner
is just a plain flex item with the default flex: 0 1 auto, so it sized to its own content (6500+px) instead of being constrained by the outer one's box, and the ScrollArea inside it had nothing bounded to scroll within. Removed the redundant inner
— SidebarInset's children (the content-header div and ScrollArea) now sit directly in its own
, which is the actual flex column that needs to size them. Also pinned ScrollArea's direction to Vertical (it defaults to Both, which was adding an unnecessary horizontal scrollbar) and moved the sizing rule in app.css off a class ScrollArea silently drops (confirmed via the same probe — a caller-supplied `class` never reaches ScrollArea's rendered DOM, only its own internal one does) onto its stable data-scroll-direction attribute instead. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01PqTJmazHBQK878vjQ4JnnF --- crates/web/assets/app.css | 9 ++++++- crates/web/src/app/mod.rs | 55 +++++++++++++++++++-------------------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/crates/web/assets/app.css b/crates/web/assets/app.css index eff0f5f..205f3db 100644 --- a/crates/web/assets/app.css +++ b/crates/web/assets/app.css @@ -2,7 +2,14 @@ body { font-family: system-ui, sans-serif; margin: 0; color: #1a1a1a; height: 10 .app-shell { display: flex; height: 100vh; } .content-header { display: flex; align-items: center; gap: 0.75rem; padding: 1.5rem 1.5rem 0.5rem; } .content-header h2 { margin: 0; } -.article-scroll-area { flex: 1; min-height: 0; padding: 0 1.5rem 1.5rem; } +/* Target the primitive's own stable data attribute, not a class we pass in: + ScrollArea sets its own `class` after spreading our attributes, so a + caller-supplied class never reaches the DOM (confirmed by inspecting the + live element — it only ever carries "dx-scroll-area-auto-hide"). Without + this, the scroll div has no bounded height, so it just grows to fit every + article (nothing to scroll) while its ancestor's overflow: hidden clips + everything past the viewport instead. */ +main > [data-scroll-direction] { flex: 1; min-height: 0; padding: 0 1.5rem 1.5rem; } .article-list { list-style: none; padding: 0; margin: 0; } .article-row { border-bottom: 1px solid #ddd; padding: 1rem 0; display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; } .article-row-header { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.5rem; } diff --git a/crates/web/src/app/mod.rs b/crates/web/src/app/mod.rs index ec6c9b0..2467f81 100644 --- a/crates/web/src/app/mod.rs +++ b/crates/web/src/app/mod.rs @@ -85,37 +85,36 @@ pub fn App() -> Element { } } SidebarInset { - main { - div { class: "content-header", - SidebarTrigger {} - h2 { - { - let heading = match selected_feed.read().as_ref() { - None => "All articles".to_string(), - Some(id) => feeds - .read() - .as_ref() - .and_then(|r| r.as_ref().ok()) - .and_then(|feeds| feeds.iter().find(|f| &f.id == id)) - .map(|f| f.title.clone()) - .unwrap_or_else(|| "Feed".to_string()), - }; - rsx! { "{heading}" } - } + div { class: "content-header", + SidebarTrigger {} + h2 { + { + let heading = match selected_feed.read().as_ref() { + None => "All articles".to_string(), + Some(id) => feeds + .read() + .as_ref() + .and_then(|r| r.as_ref().ok()) + .and_then(|feeds| feeds.iter().find(|f| &f.id == id)) + .map(|f| f.title.clone()) + .unwrap_or_else(|| "Feed".to_string()), + }; + rsx! { "{heading}" } } } - ScrollArea { class: "article-scroll-area", - match articles.read().as_ref() { - Some(Ok(articles)) => rsx! { - ul { class: "article-list", - for article in articles.iter() { - ArticleRow { article: article.clone() } - } + } + ScrollArea { + direction: dioxus_primitives::scroll_area::ScrollDirection::Vertical, + match articles.read().as_ref() { + Some(Ok(articles)) => rsx! { + ul { class: "article-list", + for article in articles.iter() { + ArticleRow { article: article.clone() } } - }, - Some(Err(err)) => rsx! { p { class: "error", "Failed to load articles: {err}" } }, - None => rsx! { p { "Loading..." } }, - } + } + }, + Some(Err(err)) => rsx! { p { class: "error", "Failed to load articles: {err}" } }, + None => rsx! { p { "Loading..." } }, } } }