From db95eab0bb4ddecff72862eeb1a4b41def4245e4 Mon Sep 17 00:00:00 2001 From: elkaix Date: Sun, 16 Aug 2026 20:55:16 -0400 Subject: [PATCH] fix(web): keep the model quick-switch menu inside the viewport The menu opens above its pill with a 160px minimum height, so a pill near the top of the viewport produced a menu that reached past the viewport edge and could not be scrolled to. Clamp the height to the space that is actually available; the 360px cap is unchanged. --- .changeset/web-model-dropdown-viewport.md | 5 +++++ apps/pythinker-web/src/components/Composer.vue | 2 +- apps/pythinker-web/test/composer.test.ts | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/web-model-dropdown-viewport.md diff --git a/.changeset/web-model-dropdown-viewport.md b/.changeset/web-model-dropdown-viewport.md new file mode 100644 index 00000000..2f529c4b --- /dev/null +++ b/.changeset/web-model-dropdown-viewport.md @@ -0,0 +1,5 @@ +--- +"@pymodel/pythinker-code": patch +--- + +Keep the web model quick-switch menu inside the viewport when the composer sits near the top of the window. diff --git a/apps/pythinker-web/src/components/Composer.vue b/apps/pythinker-web/src/components/Composer.vue index e2eba130..3216fcde 100644 --- a/apps/pythinker-web/src/components/Composer.vue +++ b/apps/pythinker-web/src/components/Composer.vue @@ -749,7 +749,7 @@ function toggleDropdown(): void { if (dropdownOpen.value) { const rect = modelPillRef.value?.getBoundingClientRect(); modelDropdownStyle.value = rect - ? { maxHeight: `${Math.min(360, Math.max(160, rect.top - 4 - 12))}px` } + ? { maxHeight: `${Math.min(360, Math.max(0, rect.top - 4 - 12))}px` } : {}; permDropdownOpen.value = false; document.addEventListener('click', onDocClick, true); diff --git a/apps/pythinker-web/test/composer.test.ts b/apps/pythinker-web/test/composer.test.ts index 1584ea95..bd187766 100644 --- a/apps/pythinker-web/test/composer.test.ts +++ b/apps/pythinker-web/test/composer.test.ts @@ -408,9 +408,11 @@ describe('Composer model dropdown', () => { const pill = wrapper.get('.model-pill'); const rect = vi.spyOn(pill.element, 'getBoundingClientRect'); + // A pill near the top of the viewport used to get a 160px menu that reached + // above the viewport edge, so its upper entries could not be scrolled to. rect.mockReturnValue({ top: 20 } as DOMRect); await pill.trigger('click'); - expect(wrapper.get('.model-dropdown').element.style.maxHeight).toBe('160px'); + expect(wrapper.get('.model-dropdown').element.style.maxHeight).toBe('4px'); rect.mockReturnValue({ top: 300 } as DOMRect); await pill.trigger('click');