Keep the settings dialog inside the window as accordion sections are opened - #398
Merged
Merged
Conversation
The settings modal grows with its content and is centred in a fixed, non-scrolling overlay, so expanding a few accordion sections pushed it past both edges of the window — and the half above the top edge, title and close button included, could not be scrolled to. Opening a section now measures the dialog and collapses the sections opened before it, oldest first, until it fits again. A module-level queue tracks expanded sections in the order they were opened, so the section just opened is last in line and the one opened longest ago gives up its space first. Details worth knowing: - The budget comes from panel-anchor's visibleViewportBottom() rather than innerHeight, and #settingsOverlay is now centred within that same visible height. Limiting only the dialog's size is not enough: centred in a layout viewport taller than the visible one, a dialog of exactly the allowed height still hangs off the bottom of the screen by half the difference. - A raised software keyboard shrinks the visible viewport exactly as a stranded layout viewport does, and tapping a header blurs the field first, so the collapse would have run on the keyboard's reading — and persisted it. panel-anchor now exports visibleViewportSettled(), and the fit does nothing until the measurement is trustworthy again. - openSettingsModal() awaits populateModalFields() before measuring. A reachable InvokeAI backend reveals the username, password and board rows a round trip later, and the old fire-and-forget call measured a modal ~170px shorter than the one the user ended up with. - .settings-modal gains a max-height and scrolls as a last resort, for the one case the collapsing cannot help: a single section taller than the window, which is never collapsed because shutting it would make it unopenable. Its budget subtracts the margin in px and hands back .modal-content's padding in em, so it agrees with the JS at any root font size. - The accordion click handler is a header.onclick property assignment rather than addEventListener, so running setupAccordions twice over the same dialog cannot stack handlers that toggle a section twice per click. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Opening enough accordion sections in the Settings dialog made it taller than the browser window. It is centred in a fixed, non-scrolling overlay, so it spilled off both edges — and the part above the top edge, the title and the ✖ close button included, could not be reached at all.
Opening a section now measures the dialog and collapses the sections opened before it, oldest first, until it fits. A queue tracks expanded sections in the order they were opened, so the section you just clicked is always the one that survives, and the one you opened longest ago is the first to go.
How it decides what "fits" means
visibleViewportBottom()(panel-anchor.js) rather thaninnerHeight, so a stranded iPadOS layout viewport does not fool it.#settingsOverlayis now centred within that same visible height. Limiting only the dialog's size is not enough: centred in a layout viewport taller than the visible one, a dialog of exactly the allowed height still hangs off the bottom of the screen by half the difference, and a fixed overlay does not scroll. Measured before the fix at layout 800 / visible 500:top 166, bottom 634— 134px unreachable. After:top 16, bottom 484.display:noneand may have been saved on a larger window.openSettingsModal()now awaitspopulateModalFields()before measuring. A reachable InvokeAI backend reveals the username, password and board rows a round trip later; the old fire-and-forget call measured a dialog ~170px shorter than the one the user ends up with, and nothing re-checked it.localStoragewhere it outlives the keyboard.panel-anchor.jsnow exportsvisibleViewportSettled()(it already held this state for its own panel corrections) and the fit does nothing until the measurement is trustworthy.max-heighton.settings-modal, which scrolls instead. Its budget subtracts the margin in px and hands back.modal-content's padding in em, so the CSS and the JS agree on the allowed height at any root font size (verified at 12/16/20/24px).Also: the accordion click handler is now a
header.onclickproperty assignment instead ofaddEventListener, so a secondsetupAccordions()pass over the same dialog cannot stack handlers that toggle a section twice per click.Testing
tests/frontend/settings-accordion-fit.test.js, 12 cases: collapse order by age rather than document order, re-opening refreshing the queue, closing collapsing nothing, the single over-tall section being kept, restore-trimming on open, measuring only after the async fields land, the keyboard guard, and an exact-budget boundary case. Mutation-checked — flipping the margin constant, dropping theawait, or dropping the keyboard guard each fail the suite.npm run lintandformat:checkclean.Known, not addressed here
.modal-closeisposition: absoluteinside what is now a scroll container, so in the single-over-tall-section fallback the ✖ scrolls out of view. The backdrop is still clickable, so it is a wart rather than a lockout.max-heightfallback catches it, but the invariant only holds at click and open time.settingsUpdatedis dispatched onwindow(state.js) but listened for ondocument(settings.js:846), soinitializeSettingsnever actually re-runs. Several comments in the file assume it does. Left alone here — fixing it would change behaviour across the whole settings module.🤖 Generated with Claude Code