From 3af11e8b4ad1aeed88e4d98edfbbc22302762d62 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 10 Sep 2026 13:31:37 +0200 Subject: [PATCH] fix: source cards fit the phone; long names wrap, no nested scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .assistant-sources was a grid with an auto column, which grows to its widest unbreakable content — so a long file name such as "2021_Fleisher_&_Ludwig's_Textbook_..." widened every card past the screen. The column is now minmax(0,1fr), cards shrink (min-width:0) and names break onto new lines (overflow-wrap:anywhere, with word-break for iOS < 15.4). On phones the list is one vertical scroll: no sideways pan and no excerpt scroll box inside it. Measured on iPhone 14 / SE, Android 360 and Pixel: before, cards reached 636px on every phone (list overflowed by 236-288px); after, the widest card ends 12px inside the screen and nothing scrolls sideways. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01W4rptBNvn6RYieQw54GXNS --- public/css/assistant.css | 13 ++++++++++--- test/assistant-mobile.test.js | 12 ++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/public/css/assistant.css b/public/css/assistant.css index 9a31ffd4..35940968 100644 --- a/public/css/assistant.css +++ b/public/css/assistant.css @@ -123,7 +123,9 @@ .assistant-image-modal-card img { max-width:100%; max-height:92vh; border-radius:14px; background:white; box-shadow:0 24px 80px rgba(0,0,0,.35); } .assistant-image-modal-close { position:absolute; top:8px; right:8px; z-index:1; width:38px; height:38px; border:0; border-radius:999px; background:white; color:var(--g800); font-size:24px; line-height:1; cursor:pointer; box-shadow:var(--shadow); } .assistant-image-modal-cancel { justify-self:center; border:0; border-radius:999px; background:white; color:var(--g800); font-weight:700; padding:9px 14px; box-shadow:var(--shadow); cursor:pointer; } -.assistant-sources { padding:10px 12px; display:grid; gap:8px; flex:1 1 auto; min-height:0; overflow-y:auto; } +/* One column capped at the panel's width: an auto grid column grows to its + widest unbreakable content, so a long file name pushed every card off-screen. */ +.assistant-sources { padding:10px 12px; display:grid; grid-template-columns:minmax(0,1fr); gap:8px; flex:1 1 auto; min-height:0; overflow-y:auto; overflow-x:hidden; } .assistant-saved-chats { flex:1 1 auto; overflow-y:auto; min-height:0; padding:10px 12px; display:flex; flex-direction:column; gap:8px; } /* Open WebUI-style recency headings above each run of chats. */ /* Composed empty state: a title, one status pill and the composer — nothing @@ -158,7 +160,9 @@ .assistant-saved-chat-title { font-size:13px; font-weight:500; color:var(--g800); line-height:1.35; display:block; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .assistant-saved-chat-meta { font-size:11px; color:var(--g500); display:block; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .assistant-save-actions { display:flex; gap:6px; flex-wrap:wrap; } -.assistant-source { border:1px solid var(--g200); border-radius:10px; padding:10px 12px; background:white; box-shadow:var(--shadow); font-size:12px; line-height:1.5; transition:border-color .12s ease; } +/* Long names break onto new lines instead of running past the card + (word-break is the fallback for iOS Safari before 15.4). */ +.assistant-source { min-width:0; overflow-wrap:anywhere; word-break:break-word; border:1px solid var(--g200); border-radius:10px; padding:10px 12px; background:white; box-shadow:var(--shadow); font-size:12px; line-height:1.5; transition:border-color .12s ease; } .assistant-source:hover { border-color:var(--blue-light); } .assistant-source:target { border-color:var(--blue); box-shadow:0 0 0 3px var(--blue-light); } .assistant-source strong { color:var(--g800); } @@ -211,7 +215,10 @@ border:none; box-shadow:none; border-radius:0; } .assistant-side .card-header { flex:0 0 auto; } /* The band is fixed height, so the list inside it is what scrolls. */ - .assistant-sources { flex:1 1 auto; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch; + /* On a touchscreen the list is one smooth vertical scroll: no sideways pan, + and no excerpt scrolling inside it. */ + .assistant-source-excerpt { max-height:none; overflow:visible; } + .assistant-sources { flex:1 1 auto; min-height:0; overflow-y:auto; overflow-x:hidden; touch-action:pan-y; -webkit-overflow-scrolling:touch; padding-bottom:calc(12px + env(safe-area-inset-bottom)); } /* Slim toolbar: icon-only actions in one row */ /* Saved chats: a fixed drawer overlaying the chat */ diff --git a/test/assistant-mobile.test.js b/test/assistant-mobile.test.js index 6e878ba7..0771576e 100644 --- a/test/assistant-mobile.test.js +++ b/test/assistant-mobile.test.js @@ -140,3 +140,15 @@ test('on a phone the menu follows the view: chat history in the assistant', () = assert.match(mobile, /#assistant-saved-chats \{ flex:1 1 auto; min-height:0; overflow-y:auto;/, 'the chat list does'); assert.match(mobile, /\.assistant-drawer-close \{ display:none !important; \}/, 'one close control, the « in the head row'); }); + +test('source cards fit the phone: long names wrap, nothing scrolls sideways or inside', () => { + const css = read('public/css/assistant.css'); + // An auto grid column grew to the longest unbroken file name, so every card + // ran off the right edge of an iPhone. + assert.match(css, /^\.assistant-sources \{[^}]*grid-template-columns:minmax\(0,1fr\);[^}]*overflow-x:hidden;/m); + assert.match(css, /^\.assistant-source \{ min-width:0; overflow-wrap:anywhere; word-break:break-word;/m, + 'long names break onto new lines, with the pre-15.4 Safari fallback'); + const mobile = css.slice(css.indexOf('@media (max-width: 640px)')); + assert.match(mobile, /\.assistant-source-excerpt \{ max-height:none; overflow:visible; \}/, 'no scroll box inside the scrolling list'); + assert.match(mobile, /\.assistant-sources \{[^}]*overflow-x:hidden; touch-action:pan-y;/, 'and no sideways pan'); +});