fix(react-ui): restore sidebar collapse in dev + stop Talk page auto-scroll (#10383)

The sidebar collapse toggle silently no-op'd in dev builds. toggleCollapse
ran its side effects (localStorage write + sidebar-collapse dispatch) inside
the setCollapsed updater. StrictMode double-invokes updaters in dev to surface
impurity, and the synchronous dispatch re-entered setState from the
App/Sidebar listeners mid-update, so the toggle never committed. Production
builds don't double-invoke, which is why only the dev server was affected.
Compute next from current state and move the persist + broadcast into the
handler body so the updater is pure.

Also fix the Talk page anchoring to the transcript box on load. The transcript
is its own overflow container, but scrollIntoView bubbles to every scrollable
ancestor including the window, yanking the whole page down on mount. Scroll
the transcript container directly instead.


Assisted-by: Claude:claude-opus-4-8 [Claude Code]

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
LocalAI [bot]
2026-06-18 00:48:56 +02:00
committed by GitHub
parent 5c2ae7857a
commit 88726f2da4
2 changed files with 13 additions and 8 deletions

View File

@@ -130,12 +130,14 @@ export default function Sidebar({ isOpen, onClose }) {
}, [location.pathname])
const toggleCollapse = () => {
setCollapsed(prev => {
const next = !prev
try { localStorage.setItem(COLLAPSED_KEY, String(next)) } catch (_) { /* ignore */ }
window.dispatchEvent(new CustomEvent('sidebar-collapse', { detail: { collapsed: next } }))
return next
})
// Side effects (persist + broadcast) live in the handler body, never inside
// the setState updater: StrictMode double-invokes updaters in dev, and the
// synchronous sidebar-collapse dispatch re-entered setState from the
// listeners mid-update, so the toggle silently no-op'd in dev builds.
const next = !collapsed
try { localStorage.setItem(COLLAPSED_KEY, String(next)) } catch (_) { /* ignore */ }
setCollapsed(next)
window.dispatchEvent(new CustomEvent('sidebar-collapse', { detail: { collapsed: next } }))
}
const toggleSection = (id) => {

View File

@@ -127,9 +127,12 @@ export default function Talk() {
.finally(() => setModelsLoading(false))
}, [])
// Auto-scroll transcript
// Auto-scroll the transcript's own overflow container. scrollIntoView bubbles
// to every scrollable ancestor (incl. the window), which yanked the whole
// page down to the transcript box on mount; scoping to the box avoids it.
useEffect(() => {
transcriptEndRef.current?.scrollIntoView({ behavior: 'smooth' })
const box = transcriptEndRef.current?.parentElement
box?.scrollTo({ top: box.scrollHeight, behavior: 'smooth' })
}, [transcript])
// Mirror Chat.jsx: connect / disconnect client MCP servers as the user toggles them.