Repository navigation
Description
Problem Statement
While reviewing #740 (merged as 845fcfea), maintainer yetone filed magpie-community/plugins#744: #740 restyled the three native <select> elements in agent-office's Settings menu (#optLang, #optScene, #optQuality) with appearance: none and an SVG chevron, but they are still native <select> elements that open OS-native popup menus. Magpie's GUI rule prohibits native <select> elements: dropdowns must use an in-page app menu (role="listbox") so visual styling, dark/light theme adaptation, keyboard interaction, and viewport clamping behave identically inside Magpie's sandboxed iframe across WKWebView, WebView2, and WebKitGTK.
Solution
Replace the three native <select> elements (#optLang, #optScene, #optQuality) in agent-office's Settings panel with custom Magpie-style app menus:
- Each picker is rendered as a
<button type="button" class="lang-select" aria-haspopup="listbox" aria-expanded="false">trigger bearing its original ID (#optLang,#optScene,#optQuality), labeled viaaria-labelledbyto its setting row title (#lblLang,#lblScene,#lblQuality), containing a text label span and the same inline<svg class="chev" viewBox="0 0 10 6">used by#settingsBtn(rotating 180deg when open). - Each trigger controls a sibling
<div class="picker-list" role="listbox" tabindex="-1" hidden>(#optLangList,#optSceneList,#optQualityList) populated with<button type="button" role="option" data-value="..." aria-selected="true|false">items and a checkmark SVG on the selected item. - Because
#settingshasmax-height: min(72vh, 520px); overflow: auto;, the open listbox usesposition: fixedviewport positioning (minWidthmatching the trigger width, opening below the trigger or flipping above when bottom viewport space is tighter than top space, clamped within8pxof viewport edges). - Keyboard and pointer interaction follow Magpie's
SelectMenu/AppMenuconventions:ArrowDown,ArrowUp,Enter,Spaceon the trigger open the listbox;ArrowDown,ArrowUp,Home,Endmove the active option inside the listbox;EnterandSpaceselect the active option and return focus to the trigger;Escapecloses only the open picker listbox and returns focus to its trigger without closing the outer#settingsmenu;Tab, capture-phase outsidepointerdown,#settingsscroll, window resize, andcloseMenus()close any open picker listbox.
flowchart TD
subgraph SettingsPanel["#settings 设置面板 (position: fixed, overflow: auto)"]
L["Language 行 (#lblLang)"] --> B1["button#optLang.lang-select\n(aria-haspopup=listbox, SVG .chev)"]
S["Scene 行 (#lblScene)"] --> B2["button#optScene.lang-select\n(aria-haspopup=listbox, SVG .chev)"]
Q["Quality 行 (#lblQuality)"] --> B3["button#optQuality.lang-select\n(aria-haspopup=listbox, SVG .chev)"]
end
subgraph PickerController["app.js 统一自定义下拉控制器 (makePicker)"]
B1 & B2 & B3 -->|"Click / Arrow / Enter / Space"| Open["展开对应 role=listbox 浮层\n(#optLangList / #optSceneList / #optQualityList)"]
Open --> Pos["fixed 视口定位与边界夹紧\n(优先向下展开,底部空间不足时自动向上翻转)"]
Pos --> Key["键盘与指针交互\n(ArrowUp/Down/Home/End/Enter/Space\nEsc 仅关下拉不关 Settings,Capture pointerdown 点外部关闭)"]
Key --> Pick["选中 role=option 条目 (含 ✓ 勾选图标)\n更新 manualLang / view.scene / view.quality → refresh() & save()"]
end
User Stories
- As a Magpie user opening the Agent Office Settings menu, I want Language, Scene, and Quality to open custom in-page app menus rather than native OS
<select>popups, so that dropdowns match Magpie's visual theme and work consistently inside the sandboxed iframe. - As a Magpie user in light or dark theme, I want the Language, Scene, and Quality triggers to display an inline SVG chevron that uses
currentColorand rotates 180 degrees while its menu is open, so that all dropdown triggers in the toolbar and Settings share unified visual feedback. - As a Magpie user on a compact viewport (such as
600x620), I want an open picker menu near the bottom of the Settings panel to flip upward or clamp within the viewport rather than being clipped by#settings's scroll container or overflowing the window. - As a keyboard user, I want to open any of the three pickers with
Enter,Space,ArrowDown, orArrowUp, navigate options withArrowDown,ArrowUp,Home, andEnd, confirm withEnterorSpace, and dismiss only the open picker withEscapewithout closing the outer Settings panel. - As a user switching languages (manually or via auto-detection), I want the trigger labels and option labels for Language, Scene, and Quality to update immediately in place across all nine supported languages.
Implementation Decisions
- Preserve
id="optLang",id="optScene", andid="optQuality"on the trigger<button>elements so existing DOM contract checks (features.test.mjs) continue to pass. - Replace the light/dark data-URI background SVG chevron rules on
.lang-selectwith the shared inline<svg class="chev" viewBox="0 0 10 6">inside each trigger button and generalize.chevhover/expanded rotation rules to cover.lang-select. - Implement a single reusable
makePickercontroller inapp.jsdriving#optLang(#optLangList),#optScene(#optSceneList), and#optQuality(#optQualityList). - Close any open picker on capture-phase
pointerdownoutside the picker wrapper (because#settingsstopsclickpropagation in bubble phase), on#settingsscroll, on window resize, and when#settingscloses. - Update
test/browser.mjsto select scenes and qualities via the custom app menu triggers and[data-value="..."]option buttons instead of.selectOption(). - Re-stamp asset fingerprints in
page/index.htmlusingscripts/stamp-page.mjs.
Testing Decisions
- Unit & contract seam (
agent-office.test.mjs,features.test.mjs): Assert thatindex.htmlcontains no<selectelement, that#optLang,#optScene, and#optQualityarearia-haspopup="listbox"buttons with inline.chevSVGs and matchingrole="listbox"containers (#optLangList,#optSceneList,#optQualityList), and thatstyle.cssrotates.chevwhenaria-expanded="true". - Browser end-to-end seam (Playwright Chromium & WebKit): Verify opening and picking options across
#optLang,#optScene, and#optQuality, keyboard navigation andEscapedismissal, viewport clamping in both light and dark themes, and zero console/page errors.
Out of Scope
- Changing
#optHide("Leave the office after"), which is already a segmented button group (.seg.small). - Modifying host-side session scanning or hook logic.
Activity
frederick-wang commentedon Nov 5, 2026
All acceptance criteria and tickets (#5) completed and verified. Upstream PR submitted: magpie-community#771.