Files
magpie/internal/gui
yetone ee8a371cf4 gui, update: saving the Settings page keeps the models hidden from each agent, and update checks go through magpie's proxy and say so (#294: 设置代理之后只有模型代理生效了,代理没有用于检查 magpie 是否有更新) — POST /api/settings carried over the models' names, levels and images but not HiddenModels, which the page never sends, so changing the theme, the language or anything else there showed every model hidden one by one on the Agents page again; it is carried over with the others. The update feed and the release came through http.DefaultTransport, proxied only because main installs netproxy first; the update client now takes netproxy.Func on a transport of its own (Settings' Proxy, else *_PROXY, else the system's). A check that failed at start-up, before a proxy was set, stayed failed for six hours: saving a new proxy now checks again through it. And the Settings row read "requests to vendors go through {proxy}", which says update checks don't: it now names both. TestSettingsSaveKeepsWhatItDoesNotSend (every field prefsKeep doesn't send survives a save of the theme alone; fails on main with HiddenModels emptied), TestUpdateGoesThroughTheProxy (feed and asset at hosts only a local proxy answers; fails without the transport), TestSettingsProxyRechecksAFailedUpdate (fails without the re-check). account-proxy, provider-proxy and click-scroll GUI tests pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, known flakes under load, pass alone).
2026-09-30 16:03:52 +08:00
..
gui, update: saving the Settings page keeps the models hidden from each agent, and update checks go through magpie's proxy and say so (#294: 设置代理之后只有模型代理生效了,代理没有用于检查 magpie 是否有更新) — POST /api/settings carried over the models' names, levels and images but not HiddenModels, which the page never sends, so changing the theme, the language or anything else there showed every model hidden one by one on the Agents page again; it is carried over with the others. The update feed and the release came through http.DefaultTransport, proxied only because main installs netproxy first; the update client now takes netproxy.Func on a transport of its own (Settings' Proxy, else *_PROXY, else the system's). A check that failed at start-up, before a proxy was set, stayed failed for six hours: saving a new proxy now checks again through it. And the Settings row read "requests to vendors go through {proxy}", which says update checks don't: it now names both. TestSettingsSaveKeepsWhatItDoesNotSend (every field prefsKeep doesn't send survives a save of the theme alone; fails on main with HiddenModels emptied), TestUpdateGoesThroughTheProxy (feed and asset at hosts only a local proxy answers; fails without the transport), TestSettingsProxyRechecksAFailedUpdate (fails without the re-check). account-proxy, provider-proxy and click-scroll GUI tests pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, known flakes under load, pass alone).
2026-09-30 16:03:52 +08:00
agents: each agent's own model list — under an agent's name on the Agents page, "Showing 5 / 32 models" (or "All 41 models") opens a list to take models out of that agent's picker one by one and put them back (@ChongkaiX on X: 在 magpie 里选哪些模型出现在 Codex 的模型列表里) — until now an agent's list could only be narrowed by whole providers or families (Visible). settings.HiddenModels holds, by agent, the catalog entries taken out, after Visible: a deny list, so a new model is shown. provider.CatalogFor drops them, so the gateway's /models, the agents' files and pickers and Codex's catalog all follow in one place, and catalog.Touched rewrites the agents' files; Codex's own ChatGPT models are dropped from the backend's list too (CodexNativeHidden), and the list's ETag and models_cache tag carry them (CodexListTag) so Codex asks again. The model an agent is set to is marked 在用 and never taken out, on the server as well. The list groups routing groups first, then providers with their logos and on/total, a long group folded; search, 全部/已显示, a group's 全部显示/全部隐藏 on hover, 恢复全部显示; a click on a row applies at once, Esc or a click elsewhere closes it, and nothing scrolls. A renamed provider's hidden models follow it. Tested with agent-models.test.cjs in Chromium and WebKit, English and Chinese; TestHiddenModels (provider), TestCodexModelListHidesOnAgentsPage (gateway) and TestAgentModelsAPI (gui) in sandbox HOMEs. click-scroll, panel-fold, old-webkit, model-pick, menu-scroll, add-button pass; GOOS=linux go vet, linux and windows builds, go test -tags nogui ./... pass. Not tried in the real Wails window or against a real Codex.
2026-09-30 12:44:59 +08:00
gui, update: saving the Settings page keeps the models hidden from each agent, and update checks go through magpie's proxy and say so (#294: 设置代理之后只有模型代理生效了,代理没有用于检查 magpie 是否有更新) — POST /api/settings carried over the models' names, levels and images but not HiddenModels, which the page never sends, so changing the theme, the language or anything else there showed every model hidden one by one on the Agents page again; it is carried over with the others. The update feed and the release came through http.DefaultTransport, proxied only because main installs netproxy first; the update client now takes netproxy.Func on a transport of its own (Settings' Proxy, else *_PROXY, else the system's). A check that failed at start-up, before a proxy was set, stayed failed for six hours: saving a new proxy now checks again through it. And the Settings row read "requests to vendors go through {proxy}", which says update checks don't: it now names both. TestSettingsSaveKeepsWhatItDoesNotSend (every field prefsKeep doesn't send survives a save of the theme alone; fails on main with HiddenModels emptied), TestUpdateGoesThroughTheProxy (feed and asset at hosts only a local proxy answers; fails without the transport), TestSettingsProxyRechecksAFailedUpdate (fails without the re-check). account-proxy, provider-proxy and click-scroll GUI tests pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, known flakes under load, pass alone).
2026-09-30 16:03:52 +08:00
settings: a Text size preference (文字大小) that makes everything in magpie's windows larger, 100/110/125/150%, with Ctrl/Cmd + = − 0 in the window and the tray panel, and Windows' text drawn in its own UI faces (Discord, Clarence Dan: the font size is hard on the eyes on Windows) — Settings → Preferences gains a Text size row under Language whose pick posts to a new POST /api/settings/text-size (saved as settings.TextSize, validated against 100/110/125/150, 0 read as 100, and kept by the Settings page's general save, which never sends it). The size is each webview's own zoom, as a browser zooms, not CSS zoom: CSS zoom leaves getBoundingClientRect and clientX in zoomed pixels while offsets, scrollTop and 100vh are not, and app.js, routing.js and library.js place menus, popovers and the panel's height by those measures; under the webview's zoom the page is laid out again in fewer, larger CSS pixels and every measure agrees. Windows and Linux use Wails' SetZoom (WebView2's zoom factor, WebKitGTK's zoom level; the Zoom window option at creation, so the pages open at the saved size), the Mac WKWebView.pageZoom through cgo (macOS 11+), since Wails' Mac zoom is magnification, which lays nothing out again. None is under 100% because Wails clamps SetZoom below 1 on Windows and Linux. The windows grow around the pages: the panel's width and the height its page asks for (still in CSS pixels) are multiplied by the zoom, capped at the screen's work area (textsize.go panelFrame), and resized at once when the size changes; the main window's minimum is 560×420 CSS pixels at the size, capped by the screen, so a window smaller than that grows. Runtime changes wait for the app to be ready (whenReady), and at startup the Mac applies the zoom once its webviews exist. The Mac header, drawn under the native traffic lights which do not grow, divides its height, left padding and the modal's top by --zoom (set from boot.js before the first paint and from the settings), so it stays 50 points tall and level with them. The shortcuts are Cmd on the Mac and Ctrl elsewhere (the other modifier does nothing), step through the sizes and 0 goes back to 100%, with a "Text size 125%" toast; in magpie web the row is hidden and the keys are left to the browser's own zoom. On Windows the page names its faces (html.win): Segoe UI Variable Text, Segoe UI, then Microsoft YaHei UI for Chinese rather than whatever WebView2 falls back to; the Mac and Linux stack is unchanged, and there is no automatic size bump. New strings in en and zh. Tested: internal/gui/tests/text-size.test.cjs in Chromium and WebKit, en and zh, light and dark — a pick posts and does not scroll the settings page, stays picked after a reload with --zoom from boot.js, the Settings page's other saves leave it, the shortcuts step and reset in the window and the panel, and at 150% (the smallest window, 840×630 points, as 560×420 CSS pixels at device scale 1.5) no view, the header or the panel runs off to the side, the Mac header is 50 points; screenshots at 100% and 150% looked at. currency.test.cjs's viewport is 480 tall instead of 420, as settings-groups' is: the new row put the Currency control under the footer, where WebKit's hover could not reach it. Go: TestTextSizeFrames (panel and window sizes) and TestTextSizeSetting (the endpoint saves, zooms the windows, rejects 133, survives the general save, boot.js carries it) in internal/gui, and the settings round trip. go build -tags nogui (and GOOS=linux, GOOS=windows, plain darwin), go test -tags nogui ./... (TestUserPath flaked once under load and passes alone) and the whole GUI suite with --test-concurrency=2 pass. Not tried in the real Wails window on any platform: the Mac pageZoom, WebView2's zoom factor with the resized panel and the Windows font are untested there.
2026-09-30 02:51:17 +08:00
settings: a Text size preference (文字大小) that makes everything in magpie's windows larger, 100/110/125/150%, with Ctrl/Cmd + = − 0 in the window and the tray panel, and Windows' text drawn in its own UI faces (Discord, Clarence Dan: the font size is hard on the eyes on Windows) — Settings → Preferences gains a Text size row under Language whose pick posts to a new POST /api/settings/text-size (saved as settings.TextSize, validated against 100/110/125/150, 0 read as 100, and kept by the Settings page's general save, which never sends it). The size is each webview's own zoom, as a browser zooms, not CSS zoom: CSS zoom leaves getBoundingClientRect and clientX in zoomed pixels while offsets, scrollTop and 100vh are not, and app.js, routing.js and library.js place menus, popovers and the panel's height by those measures; under the webview's zoom the page is laid out again in fewer, larger CSS pixels and every measure agrees. Windows and Linux use Wails' SetZoom (WebView2's zoom factor, WebKitGTK's zoom level; the Zoom window option at creation, so the pages open at the saved size), the Mac WKWebView.pageZoom through cgo (macOS 11+), since Wails' Mac zoom is magnification, which lays nothing out again. None is under 100% because Wails clamps SetZoom below 1 on Windows and Linux. The windows grow around the pages: the panel's width and the height its page asks for (still in CSS pixels) are multiplied by the zoom, capped at the screen's work area (textsize.go panelFrame), and resized at once when the size changes; the main window's minimum is 560×420 CSS pixels at the size, capped by the screen, so a window smaller than that grows. Runtime changes wait for the app to be ready (whenReady), and at startup the Mac applies the zoom once its webviews exist. The Mac header, drawn under the native traffic lights which do not grow, divides its height, left padding and the modal's top by --zoom (set from boot.js before the first paint and from the settings), so it stays 50 points tall and level with them. The shortcuts are Cmd on the Mac and Ctrl elsewhere (the other modifier does nothing), step through the sizes and 0 goes back to 100%, with a "Text size 125%" toast; in magpie web the row is hidden and the keys are left to the browser's own zoom. On Windows the page names its faces (html.win): Segoe UI Variable Text, Segoe UI, then Microsoft YaHei UI for Chinese rather than whatever WebView2 falls back to; the Mac and Linux stack is unchanged, and there is no automatic size bump. New strings in en and zh. Tested: internal/gui/tests/text-size.test.cjs in Chromium and WebKit, en and zh, light and dark — a pick posts and does not scroll the settings page, stays picked after a reload with --zoom from boot.js, the Settings page's other saves leave it, the shortcuts step and reset in the window and the panel, and at 150% (the smallest window, 840×630 points, as 560×420 CSS pixels at device scale 1.5) no view, the header or the panel runs off to the side, the Mac header is 50 points; screenshots at 100% and 150% looked at. currency.test.cjs's viewport is 480 tall instead of 420, as settings-groups' is: the new row put the Currency control under the footer, where WebKit's hover could not reach it. Go: TestTextSizeFrames (panel and window sizes) and TestTextSizeSetting (the endpoint saves, zooms the windows, rejects 133, survives the general save, boot.js carries it) in internal/gui, and the settings round trip. go build -tags nogui (and GOOS=linux, GOOS=windows, plain darwin), go test -tags nogui ./... (TestUserPath flaked once under load and passes alone) and the whole GUI suite with --test-concurrency=2 pass. Not tried in the real Wails window on any platform: the Mac pageZoom, WebView2's zoom factor with the resized panel and the Windows font are untested there.
2026-09-30 02:51:17 +08:00
omarchy: on Omarchy, magpie is drawn in Omarchy's own design language, follows its theme, drops its panel under the bar as Omarchy's own menus do, and can put its icon in Omarchy's bar (让整个UI/UX完全符合Omarchy的设计语言…单独对Omarchy做这个支持,现在的UI/UX当然要保留) — on Hyprland magpie looked like any other GTK app: its own light/dark palette, rounded shadowed popups, and a tray panel floated framed and dimmed in the middle of the screen, labelled with the program's name, never closing on a click elsewhere (focus follows the mouse there, so Wails turns off focus-lost hiding), and its tray icon was hidden in the bar's drawer. Now, only when Omarchy is detected (internal/omarchy: ~/.local/share/omarchy and its current theme), boot.js hands the page the current theme (colors.toml, the shell's theme TOML, Hyprland's border size, colour and rounding, Omarchy's font) as CSS variables before the first paint, and omarchy.css, all under html.omarchy, draws flat as Omarchy's shell does: the theme's background, no shadows or blur, corners as round as Hyprland's windows, popups and dialogs framed by the active border's colour, uppercase letter-spaced labels, rows and picks as a faint fill with the text in the accent. /api/omarchy is polled every 2.5s, so a theme picked in Omarchy's menu reaches the windows at once; Settings → Appearance shows the theme's name instead of Light/Dark/System. Under Hyprland the tray panel gets a title of its own ("magpie panel", set through GTK since Wails leaves frameless windows untitled), and each opening gives Hyprland a named Lua window rule placing it under the bar at the click, 6px in from the edges, pinned, opaque, borderless; its height is capped to the work area. While it is open a non-consuming left-click bind emits a custom event on Hyprland's socket2; a click outside the panel and below the bar closes it (the bar is left to its icons, so the icon toggles), and the bind is removed on close or, after Escape, on the next click. The tray item's SNI id is "magpie" instead of "Wails", so it can be pinned. Settings → Bar icon (状态栏图标), shown only in the app on Omarchy's Hyprland, puts an Omarchy bar-widget plugin (usemagpie.magpie, a bird glyph, in ~/.config/omarchy/plugins, then rescanPlugins and plugin enable) beside Omarchy's own icons: a click runs magpie panel, a right-click magpie app; Off disables and removes it; a widget pointing at a moved or older magpie is rewritten at start. magpie panel (new) opens the quick panel, starting magpie if needed, or toggles it in the running instance. Elsewhere nothing changes. Tested in a Hyper-V VM on the latest Omarchy (Hyprland 0.56, 2528×960 at scale 1.6): the look with tokyo-night; the panel at [1134,32] under the bar; clicks on the panel and the bar keep it open, a click on another window closes it, the icon toggles it, Escape closes it and the bind goes; Bar icon On adds the bird to the bar, its click opens the panel under it (also with magpie not running), a second click closes it, right-click opens the window, Off removes it; autostart becomes app-magpie@autostart through systemd's XDG generator; the launcher entry and icon are there. Go: internal/omarchy tests (theme parsing, panel placement on scaled and turned monitors, click-outside geometry, widget files quoted with the executable, KeepWidget rewriting only a stale one). A new internal/gui/tests/omarchy.test.cjs, Chromium and WebKit, en and zh: with no Omarchy theme the page has no omarchy class or theme style, the Appearance choices show, no Bar icon row, nothing asked of /api/omarchy; with one, the theme's background, square corners, its name, and Bar icon's On and Off each posted with nothing scrolled, hidden when the bar isn't there. old-webkit now lets omarchy.js load in the head beside boot.js (it does nothing off Omarchy). The full GUI suite, gofmt, go vet, go build (GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried: other Hyprland setups than Omarchy's, a multi-monitor VM, and the GTK4 build in the VM (gtk3 was used).
2026-09-30 04:58:11 +08:00
gui: the Windows tray panel no longer shows a faint close X over its Settings gear — its caption buttons are hidden there (#238 mintonight: a blurry red X at the panel's top-right corner, just above and right of the gear) — Wails v3 makes a frameless window on Windows WS_OVERLAPPEDWINDOW and, for the shadow and round corners, extends DWM's frame into the whole client area; the panel left its close button at the zero value, ButtonEnabled, which sets WS_SYSMENU, so DWM drew the caption's close X into that extended frame at the window's top right, and with the panel translucent (BackgroundTypeTranslucent, WS_EX_NOREDIRECTIONBITMAP, a see-through webview) it showed through the page, faint, over the gear. It was not the page: #winclose is removed everywhere but Linux's window, and is a grey pill in the header row. The panel's options move into panelOptions(goos, theme), unchanged, and on Windows its close, minimise and maximise buttons are ButtonHidden, which takes WS_SYSMENU and the min/max boxes off, so DWM has no caption buttons to draw; DisableFramelessWindowDecorations stays off, so the shadow and Windows 11's round corners stay. The Mac and Linux get the same options as before (their button states left at the zero value), and the main window is not touched. Tested with a new internal/gui/panel_options_test.go (!nogui): on windows the three buttons are hidden and the panel still frameless, translucent, off the taskbar and decorated; on darwin and linux the buttons are as before and URL, backdrop and corner radius unchanged; run on the Mac and compiled for Windows (GOOS=windows go test -c). go build -tags nogui (and GOOS=linux), GOOS=windows go build and go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut and TestClaudeAccountNamedAsItsLogin timed out once at a load average near 60 and pass alone). Not tried on a real Windows desktop: that DWM stops drawing the X without WS_SYSMENU is from the Win32 custom-frame behaviour and Wails' code, not seen.
2026-09-30 02:15:48 +08:00
agents: a provider switched off or removed, or its last account signed out, moves the agents on its models to others (#200: 希望关闭某个llm provider的时候,更改agent选择的model到可用的model … 关闭这个provider之后,第一次使用会报错,因为copilot/ 开头的模型不存在) — switching Copilot off took copilot/… out of the catalog and the agents' model lists, but an agent picked on one kept it in its config and its next request was refused as switched off. Now every field on one of magpie's models (the model, Claude Code's opus/sonnet/haiku/fable tiers, any other) whose model the change stopped serving is moved: to the same model from a provider still on, the same id first, else one spelt another way (claude-sonnet-4.5 and claude-sonnet-4-5-20250929), else back to the agent's own default (a Claude Code tier: following the main model). It is done around switching off, removing, a key removed or off, and a sign-in signed out or off, in the window, the tray panel, the TUI and the CLI; the page's status line, the TUI and the CLI say which agent went to what. A session started before the switch still asks for the old model: the gateway now takes a switched-off provider's model as unserved, so Claude Code's stand-in (its model as set now) answers it rather than a refusal. Tested in a sandbox HOME: Claude Code on the main model and three tiers plus Hermes, switched off and removed (same id, other spelling, default, a tier following), switched on again moving nothing; /api/provider/off returns the moves; the gateway serves a switched-off model through the stand-in (fails without the change) and still refuses it to an agent left on it
2026-09-29 17:56:43 +08:00
agents: a model picked for Claude Code (or any agent) shows on its row at once, in the window and the tray panel, and /api/set answers in ~0.1s instead of seconds (Discord: switching Claude Code's model took 5–10s to show, magpie 0.1.410+) — /api/set writes the config and answers with the whole state, and the frontend drew the pick only from that answer. Every state asked the devin CLI (devin models list, ~1.5–3s a run) again several times whenever Devin was installed: its model's family, its options, its effort's options and Drift's re-read, and a CLI that gave no list (signed out, offline) was asked anew on every state, since only a list was cached. With a 4s CLI one set took 28s (measured, before). Now DevinFamilies keeps a failure for a minute, serves a list past its 5 minutes while it is read again in the background, and only a caller with nothing known waits — never one marked DevinNoWait, which Devin's field reads and options are (its Set still waits, so what is written is unchanged). Goose's detection read a binary's whole build info on every state (~150ms); the answer is kept while the file's size and time are the same. In the page, commit() draws the picked model and its flash before /api/set is sent, then draws the answer (a later pick's answer winning over an earlier one's); a refused pick puts the old model back and gives the reason. What is written to the agent's config is as before. Measured in a sandbox HOME with magpie web: /api/state 2.8–3.1s and /api/set 2.9s before, 0.1–0.5s after (state() ~110ms steady in-process); a provider whose endpoint never answers made no difference either way. Tested with internal/gui/set_fast_unix_test.go (devin CLI that sleeps 4s and a provider that never answers: three sets and the states after them each under 1.5s, ~5ms, settings.json as before; fails before the fix at 28s a set) and internal/gui/tests/model-pick.test.cjs (a set held 2.5s: the pick shown within 600ms in the window scrolled and in the tray panel's opened row and line, the page not moved, the answer keeping it, a refusal restoring the old model, en and zh, Chromium and WebKit; fails on main). click-scroll, panel-fold, menu-scroll, agy-launch, cli-update and old-webkit pass. go build -tags nogui (and GOOS=linux, GOOS=windows), go vet and go test -tags nogui ./... pass (TestClaudeAccountNamedAsItsLogin failed under load and passes alone). Not tried in the real Wails window or tray, nor with a real signed-in Devin CLI.
2026-09-30 03:21:24 +08:00
gui, update: saving the Settings page keeps the models hidden from each agent, and update checks go through magpie's proxy and say so (#294: 设置代理之后只有模型代理生效了,代理没有用于检查 magpie 是否有更新) — POST /api/settings carried over the models' names, levels and images but not HiddenModels, which the page never sends, so changing the theme, the language or anything else there showed every model hidden one by one on the Agents page again; it is carried over with the others. The update feed and the release came through http.DefaultTransport, proxied only because main installs netproxy first; the update client now takes netproxy.Func on a transport of its own (Settings' Proxy, else *_PROXY, else the system's). A check that failed at start-up, before a proxy was set, stayed failed for six hours: saving a new proxy now checks again through it. And the Settings row read "requests to vendors go through {proxy}", which says update checks don't: it now names both. TestSettingsSaveKeepsWhatItDoesNotSend (every field prefsKeep doesn't send survives a save of the theme alone; fails on main with HiddenModels emptied), TestUpdateGoesThroughTheProxy (feed and asset at hosts only a local proxy answers; fails without the transport), TestSettingsProxyRechecksAFailedUpdate (fails without the re-check). account-proxy, provider-proxy and click-scroll GUI tests pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, known flakes under load, pass alone).
2026-09-30 16:03:52 +08:00
settings: a Text size preference (文字大小) that makes everything in magpie's windows larger, 100/110/125/150%, with Ctrl/Cmd + = − 0 in the window and the tray panel, and Windows' text drawn in its own UI faces (Discord, Clarence Dan: the font size is hard on the eyes on Windows) — Settings → Preferences gains a Text size row under Language whose pick posts to a new POST /api/settings/text-size (saved as settings.TextSize, validated against 100/110/125/150, 0 read as 100, and kept by the Settings page's general save, which never sends it). The size is each webview's own zoom, as a browser zooms, not CSS zoom: CSS zoom leaves getBoundingClientRect and clientX in zoomed pixels while offsets, scrollTop and 100vh are not, and app.js, routing.js and library.js place menus, popovers and the panel's height by those measures; under the webview's zoom the page is laid out again in fewer, larger CSS pixels and every measure agrees. Windows and Linux use Wails' SetZoom (WebView2's zoom factor, WebKitGTK's zoom level; the Zoom window option at creation, so the pages open at the saved size), the Mac WKWebView.pageZoom through cgo (macOS 11+), since Wails' Mac zoom is magnification, which lays nothing out again. None is under 100% because Wails clamps SetZoom below 1 on Windows and Linux. The windows grow around the pages: the panel's width and the height its page asks for (still in CSS pixels) are multiplied by the zoom, capped at the screen's work area (textsize.go panelFrame), and resized at once when the size changes; the main window's minimum is 560×420 CSS pixels at the size, capped by the screen, so a window smaller than that grows. Runtime changes wait for the app to be ready (whenReady), and at startup the Mac applies the zoom once its webviews exist. The Mac header, drawn under the native traffic lights which do not grow, divides its height, left padding and the modal's top by --zoom (set from boot.js before the first paint and from the settings), so it stays 50 points tall and level with them. The shortcuts are Cmd on the Mac and Ctrl elsewhere (the other modifier does nothing), step through the sizes and 0 goes back to 100%, with a "Text size 125%" toast; in magpie web the row is hidden and the keys are left to the browser's own zoom. On Windows the page names its faces (html.win): Segoe UI Variable Text, Segoe UI, then Microsoft YaHei UI for Chinese rather than whatever WebView2 falls back to; the Mac and Linux stack is unchanged, and there is no automatic size bump. New strings in en and zh. Tested: internal/gui/tests/text-size.test.cjs in Chromium and WebKit, en and zh, light and dark — a pick posts and does not scroll the settings page, stays picked after a reload with --zoom from boot.js, the Settings page's other saves leave it, the shortcuts step and reset in the window and the panel, and at 150% (the smallest window, 840×630 points, as 560×420 CSS pixels at device scale 1.5) no view, the header or the panel runs off to the side, the Mac header is 50 points; screenshots at 100% and 150% looked at. currency.test.cjs's viewport is 480 tall instead of 420, as settings-groups' is: the new row put the Currency control under the footer, where WebKit's hover could not reach it. Go: TestTextSizeFrames (panel and window sizes) and TestTextSizeSetting (the endpoint saves, zooms the windows, rejects 133, survives the general save, boot.js carries it) in internal/gui, and the settings round trip. go build -tags nogui (and GOOS=linux, GOOS=windows, plain darwin), go test -tags nogui ./... (TestUserPath flaked once under load and passes alone) and the whole GUI suite with --test-concurrency=2 pass. Not tried in the real Wails window on any platform: the Mac pageZoom, WebView2's zoom factor with the resized panel and the Windows font are untested there.
2026-09-30 02:51:17 +08:00
settings: a Text size preference (文字大小) that makes everything in magpie's windows larger, 100/110/125/150%, with Ctrl/Cmd + = − 0 in the window and the tray panel, and Windows' text drawn in its own UI faces (Discord, Clarence Dan: the font size is hard on the eyes on Windows) — Settings → Preferences gains a Text size row under Language whose pick posts to a new POST /api/settings/text-size (saved as settings.TextSize, validated against 100/110/125/150, 0 read as 100, and kept by the Settings page's general save, which never sends it). The size is each webview's own zoom, as a browser zooms, not CSS zoom: CSS zoom leaves getBoundingClientRect and clientX in zoomed pixels while offsets, scrollTop and 100vh are not, and app.js, routing.js and library.js place menus, popovers and the panel's height by those measures; under the webview's zoom the page is laid out again in fewer, larger CSS pixels and every measure agrees. Windows and Linux use Wails' SetZoom (WebView2's zoom factor, WebKitGTK's zoom level; the Zoom window option at creation, so the pages open at the saved size), the Mac WKWebView.pageZoom through cgo (macOS 11+), since Wails' Mac zoom is magnification, which lays nothing out again. None is under 100% because Wails clamps SetZoom below 1 on Windows and Linux. The windows grow around the pages: the panel's width and the height its page asks for (still in CSS pixels) are multiplied by the zoom, capped at the screen's work area (textsize.go panelFrame), and resized at once when the size changes; the main window's minimum is 560×420 CSS pixels at the size, capped by the screen, so a window smaller than that grows. Runtime changes wait for the app to be ready (whenReady), and at startup the Mac applies the zoom once its webviews exist. The Mac header, drawn under the native traffic lights which do not grow, divides its height, left padding and the modal's top by --zoom (set from boot.js before the first paint and from the settings), so it stays 50 points tall and level with them. The shortcuts are Cmd on the Mac and Ctrl elsewhere (the other modifier does nothing), step through the sizes and 0 goes back to 100%, with a "Text size 125%" toast; in magpie web the row is hidden and the keys are left to the browser's own zoom. On Windows the page names its faces (html.win): Segoe UI Variable Text, Segoe UI, then Microsoft YaHei UI for Chinese rather than whatever WebView2 falls back to; the Mac and Linux stack is unchanged, and there is no automatic size bump. New strings in en and zh. Tested: internal/gui/tests/text-size.test.cjs in Chromium and WebKit, en and zh, light and dark — a pick posts and does not scroll the settings page, stays picked after a reload with --zoom from boot.js, the Settings page's other saves leave it, the shortcuts step and reset in the window and the panel, and at 150% (the smallest window, 840×630 points, as 560×420 CSS pixels at device scale 1.5) no view, the header or the panel runs off to the side, the Mac header is 50 points; screenshots at 100% and 150% looked at. currency.test.cjs's viewport is 480 tall instead of 420, as settings-groups' is: the new row put the Currency control under the footer, where WebKit's hover could not reach it. Go: TestTextSizeFrames (panel and window sizes) and TestTextSizeSetting (the endpoint saves, zooms the windows, rejects 133, survives the general save, boot.js carries it) in internal/gui, and the settings round trip. go build -tags nogui (and GOOS=linux, GOOS=windows, plain darwin), go test -tags nogui ./... (TestUserPath flaked once under load and passes alone) and the whole GUI suite with --test-concurrency=2 pass. Not tried in the real Wails window on any platform: the Mac pageZoom, WebView2's zoom factor with the resized panel and the Windows font are untested there.
2026-09-30 02:51:17 +08:00
omarchy: on Omarchy, magpie is drawn in Omarchy's own design language, follows its theme, drops its panel under the bar as Omarchy's own menus do, and can put its icon in Omarchy's bar (让整个UI/UX完全符合Omarchy的设计语言…单独对Omarchy做这个支持,现在的UI/UX当然要保留) — on Hyprland magpie looked like any other GTK app: its own light/dark palette, rounded shadowed popups, and a tray panel floated framed and dimmed in the middle of the screen, labelled with the program's name, never closing on a click elsewhere (focus follows the mouse there, so Wails turns off focus-lost hiding), and its tray icon was hidden in the bar's drawer. Now, only when Omarchy is detected (internal/omarchy: ~/.local/share/omarchy and its current theme), boot.js hands the page the current theme (colors.toml, the shell's theme TOML, Hyprland's border size, colour and rounding, Omarchy's font) as CSS variables before the first paint, and omarchy.css, all under html.omarchy, draws flat as Omarchy's shell does: the theme's background, no shadows or blur, corners as round as Hyprland's windows, popups and dialogs framed by the active border's colour, uppercase letter-spaced labels, rows and picks as a faint fill with the text in the accent. /api/omarchy is polled every 2.5s, so a theme picked in Omarchy's menu reaches the windows at once; Settings → Appearance shows the theme's name instead of Light/Dark/System. Under Hyprland the tray panel gets a title of its own ("magpie panel", set through GTK since Wails leaves frameless windows untitled), and each opening gives Hyprland a named Lua window rule placing it under the bar at the click, 6px in from the edges, pinned, opaque, borderless; its height is capped to the work area. While it is open a non-consuming left-click bind emits a custom event on Hyprland's socket2; a click outside the panel and below the bar closes it (the bar is left to its icons, so the icon toggles), and the bind is removed on close or, after Escape, on the next click. The tray item's SNI id is "magpie" instead of "Wails", so it can be pinned. Settings → Bar icon (状态栏图标), shown only in the app on Omarchy's Hyprland, puts an Omarchy bar-widget plugin (usemagpie.magpie, a bird glyph, in ~/.config/omarchy/plugins, then rescanPlugins and plugin enable) beside Omarchy's own icons: a click runs magpie panel, a right-click magpie app; Off disables and removes it; a widget pointing at a moved or older magpie is rewritten at start. magpie panel (new) opens the quick panel, starting magpie if needed, or toggles it in the running instance. Elsewhere nothing changes. Tested in a Hyper-V VM on the latest Omarchy (Hyprland 0.56, 2528×960 at scale 1.6): the look with tokyo-night; the panel at [1134,32] under the bar; clicks on the panel and the bar keep it open, a click on another window closes it, the icon toggles it, Escape closes it and the bind goes; Bar icon On adds the bird to the bar, its click opens the panel under it (also with magpie not running), a second click closes it, right-click opens the window, Off removes it; autostart becomes app-magpie@autostart through systemd's XDG generator; the launcher entry and icon are there. Go: internal/omarchy tests (theme parsing, panel placement on scaled and turned monitors, click-outside geometry, widget files quoted with the executable, KeepWidget rewriting only a stale one). A new internal/gui/tests/omarchy.test.cjs, Chromium and WebKit, en and zh: with no Omarchy theme the page has no omarchy class or theme style, the Appearance choices show, no Bar icon row, nothing asked of /api/omarchy; with one, the theme's background, square corners, its name, and Bar icon's On and Off each posted with nothing scrolled, hidden when the bar isn't there. old-webkit now lets omarchy.js load in the head beside boot.js (it does nothing off Omarchy). The full GUI suite, gofmt, go vet, go build (GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried: other Hyprland setups than Omarchy's, a multi-monitor VM, and the GTK4 build in the VM (gtk3 was used).
2026-09-30 04:58:11 +08:00
omarchy: on Omarchy, magpie is drawn in Omarchy's own design language, follows its theme, drops its panel under the bar as Omarchy's own menus do, and can put its icon in Omarchy's bar (让整个UI/UX完全符合Omarchy的设计语言…单独对Omarchy做这个支持,现在的UI/UX当然要保留) — on Hyprland magpie looked like any other GTK app: its own light/dark palette, rounded shadowed popups, and a tray panel floated framed and dimmed in the middle of the screen, labelled with the program's name, never closing on a click elsewhere (focus follows the mouse there, so Wails turns off focus-lost hiding), and its tray icon was hidden in the bar's drawer. Now, only when Omarchy is detected (internal/omarchy: ~/.local/share/omarchy and its current theme), boot.js hands the page the current theme (colors.toml, the shell's theme TOML, Hyprland's border size, colour and rounding, Omarchy's font) as CSS variables before the first paint, and omarchy.css, all under html.omarchy, draws flat as Omarchy's shell does: the theme's background, no shadows or blur, corners as round as Hyprland's windows, popups and dialogs framed by the active border's colour, uppercase letter-spaced labels, rows and picks as a faint fill with the text in the accent. /api/omarchy is polled every 2.5s, so a theme picked in Omarchy's menu reaches the windows at once; Settings → Appearance shows the theme's name instead of Light/Dark/System. Under Hyprland the tray panel gets a title of its own ("magpie panel", set through GTK since Wails leaves frameless windows untitled), and each opening gives Hyprland a named Lua window rule placing it under the bar at the click, 6px in from the edges, pinned, opaque, borderless; its height is capped to the work area. While it is open a non-consuming left-click bind emits a custom event on Hyprland's socket2; a click outside the panel and below the bar closes it (the bar is left to its icons, so the icon toggles), and the bind is removed on close or, after Escape, on the next click. The tray item's SNI id is "magpie" instead of "Wails", so it can be pinned. Settings → Bar icon (状态栏图标), shown only in the app on Omarchy's Hyprland, puts an Omarchy bar-widget plugin (usemagpie.magpie, a bird glyph, in ~/.config/omarchy/plugins, then rescanPlugins and plugin enable) beside Omarchy's own icons: a click runs magpie panel, a right-click magpie app; Off disables and removes it; a widget pointing at a moved or older magpie is rewritten at start. magpie panel (new) opens the quick panel, starting magpie if needed, or toggles it in the running instance. Elsewhere nothing changes. Tested in a Hyper-V VM on the latest Omarchy (Hyprland 0.56, 2528×960 at scale 1.6): the look with tokyo-night; the panel at [1134,32] under the bar; clicks on the panel and the bar keep it open, a click on another window closes it, the icon toggles it, Escape closes it and the bind goes; Bar icon On adds the bird to the bar, its click opens the panel under it (also with magpie not running), a second click closes it, right-click opens the window, Off removes it; autostart becomes app-magpie@autostart through systemd's XDG generator; the launcher entry and icon are there. Go: internal/omarchy tests (theme parsing, panel placement on scaled and turned monitors, click-outside geometry, widget files quoted with the executable, KeepWidget rewriting only a stale one). A new internal/gui/tests/omarchy.test.cjs, Chromium and WebKit, en and zh: with no Omarchy theme the page has no omarchy class or theme style, the Appearance choices show, no Bar icon row, nothing asked of /api/omarchy; with one, the theme's background, square corners, its name, and Bar icon's On and Off each posted with nothing scrolled, hidden when the bar isn't there. old-webkit now lets omarchy.js load in the head beside boot.js (it does nothing off Omarchy). The full GUI suite, gofmt, go vet, go build (GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried: other Hyprland setups than Omarchy's, a multi-monitor VM, and the GTK4 build in the VM (gtk3 was used).
2026-09-30 04:58:11 +08:00
omarchy: on Omarchy, magpie is drawn in Omarchy's own design language, follows its theme, drops its panel under the bar as Omarchy's own menus do, and can put its icon in Omarchy's bar (让整个UI/UX完全符合Omarchy的设计语言…单独对Omarchy做这个支持,现在的UI/UX当然要保留) — on Hyprland magpie looked like any other GTK app: its own light/dark palette, rounded shadowed popups, and a tray panel floated framed and dimmed in the middle of the screen, labelled with the program's name, never closing on a click elsewhere (focus follows the mouse there, so Wails turns off focus-lost hiding), and its tray icon was hidden in the bar's drawer. Now, only when Omarchy is detected (internal/omarchy: ~/.local/share/omarchy and its current theme), boot.js hands the page the current theme (colors.toml, the shell's theme TOML, Hyprland's border size, colour and rounding, Omarchy's font) as CSS variables before the first paint, and omarchy.css, all under html.omarchy, draws flat as Omarchy's shell does: the theme's background, no shadows or blur, corners as round as Hyprland's windows, popups and dialogs framed by the active border's colour, uppercase letter-spaced labels, rows and picks as a faint fill with the text in the accent. /api/omarchy is polled every 2.5s, so a theme picked in Omarchy's menu reaches the windows at once; Settings → Appearance shows the theme's name instead of Light/Dark/System. Under Hyprland the tray panel gets a title of its own ("magpie panel", set through GTK since Wails leaves frameless windows untitled), and each opening gives Hyprland a named Lua window rule placing it under the bar at the click, 6px in from the edges, pinned, opaque, borderless; its height is capped to the work area. While it is open a non-consuming left-click bind emits a custom event on Hyprland's socket2; a click outside the panel and below the bar closes it (the bar is left to its icons, so the icon toggles), and the bind is removed on close or, after Escape, on the next click. The tray item's SNI id is "magpie" instead of "Wails", so it can be pinned. Settings → Bar icon (状态栏图标), shown only in the app on Omarchy's Hyprland, puts an Omarchy bar-widget plugin (usemagpie.magpie, a bird glyph, in ~/.config/omarchy/plugins, then rescanPlugins and plugin enable) beside Omarchy's own icons: a click runs magpie panel, a right-click magpie app; Off disables and removes it; a widget pointing at a moved or older magpie is rewritten at start. magpie panel (new) opens the quick panel, starting magpie if needed, or toggles it in the running instance. Elsewhere nothing changes. Tested in a Hyper-V VM on the latest Omarchy (Hyprland 0.56, 2528×960 at scale 1.6): the look with tokyo-night; the panel at [1134,32] under the bar; clicks on the panel and the bar keep it open, a click on another window closes it, the icon toggles it, Escape closes it and the bind goes; Bar icon On adds the bird to the bar, its click opens the panel under it (also with magpie not running), a second click closes it, right-click opens the window, Off removes it; autostart becomes app-magpie@autostart through systemd's XDG generator; the launcher entry and icon are there. Go: internal/omarchy tests (theme parsing, panel placement on scaled and turned monitors, click-outside geometry, widget files quoted with the executable, KeepWidget rewriting only a stale one). A new internal/gui/tests/omarchy.test.cjs, Chromium and WebKit, en and zh: with no Omarchy theme the page has no omarchy class or theme style, the Appearance choices show, no Bar icon row, nothing asked of /api/omarchy; with one, the theme's background, square corners, its name, and Bar icon's On and Off each posted with nothing scrolled, hidden when the bar isn't there. old-webkit now lets omarchy.js load in the head beside boot.js (it does nothing off Omarchy). The full GUI suite, gofmt, go vet, go build (GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried: other Hyprland setups than Omarchy's, a multi-monitor VM, and the GTK4 build in the VM (gtk3 was used).
2026-09-30 04:58:11 +08:00
panel: a Routing tab shows the gateway's latest requests, and the allowances' tab is named Allowances (v on Discord #feedback: the panel's Usage tab and the window's Usage page mean different things, and the panel had no routing) — the tray panel had Agents, Usage and Profiles, its Usage being each subscription's allowance while the window's Usage counts requests and Routing shows where they went. The panel's tab now reads Allowances (额度; the window keeps Usage/用量, and the tab's id and remembered choice are unchanged), and a new Routing tab (路由) lists the gateway's last 8 requests as they come, from the trace the Routing page already long-polls (/api/gateway/trace, which routing.js polled in the panel too; no new endpoint): each a two-line row with the agent's dot, icon and name and the model it asked for, the time, then where it went (provider · account or key), the model that try sent and, when the reply named another model (the new served/swapped fields), the amber "served gpt-6-luna" (实际 …) tag of the Requests list, beside the tokens and time taken; a failed one says its status and why (429 · rate limited), one under way who is answering. Over them, today's calls and tokens (the Usage page's /api/usage?period=today, read when the tab is shown and after a request ends, at most every 5 s) and Open Routing. A click on a request opens the window's Routing page on it: POST /api/window/main takes req beside view=routing (mainView lets only a positive id through), and the page, loaded so, picks that request as a click in its list would (its story told) and drops req from the address. The panel's height counts the tab as its tallest, as the others; the tab redraws keeping the panel's scroll, the click moves nothing, no border stripe. Tested with a new internal/gui/tests/panel-routing.test.cjs in Chromium and WebKit, en and zh: the Routing and Allowances labels, four fixture requests listed newest first with agent, model, "Relay · team" and "Claude · ann@example.com", the served tag on the swapped one only (not on a dated name), the 429 row, times, no row overflowing, today's 42 calls · 1.2M tokens, a click with the panel scrolled asking for view=routing&req=100 and leaving the scroll as it was, Open Routing, and the window opened on ?view=routing&req=101 with that request picked and its story shown; and TestMainView. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... and the whole GUI suite (102 tests) pass. Not tried in the real tray panel or with a live gateway: the window opening on the request is tested in the page, not through Wails.
2026-09-29 20:22:19 +08:00
gui, update: saving the Settings page keeps the models hidden from each agent, and update checks go through magpie's proxy and say so (#294: 设置代理之后只有模型代理生效了,代理没有用于检查 magpie 是否有更新) — POST /api/settings carried over the models' names, levels and images but not HiddenModels, which the page never sends, so changing the theme, the language or anything else there showed every model hidden one by one on the Agents page again; it is carried over with the others. The update feed and the release came through http.DefaultTransport, proxied only because main installs netproxy first; the update client now takes netproxy.Func on a transport of its own (Settings' Proxy, else *_PROXY, else the system's). A check that failed at start-up, before a proxy was set, stayed failed for six hours: saving a new proxy now checks again through it. And the Settings row read "requests to vendors go through {proxy}", which says update checks don't: it now names both. TestSettingsSaveKeepsWhatItDoesNotSend (every field prefsKeep doesn't send survives a save of the theme alone; fails on main with HiddenModels emptied), TestUpdateGoesThroughTheProxy (feed and asset at hosts only a local proxy answers; fails without the transport), TestSettingsProxyRechecksAFailedUpdate (fails without the re-check). account-proxy, provider-proxy and click-scroll GUI tests pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, known flakes under load, pass alone).
2026-09-30 16:03:52 +08:00
usage: a Requests tab lists every request of the period, one row each, and exports them as CSV, from the window and from magpie usage --csv (Discord 范不着: a per-request ledger as sub2api keeps, to set beside the upstream's own bill) — the Usage page summed calls by agent and model, and the log did not keep the model the agent asked for or the one the reply said answered, so a single request could not be checked against a vendor's bill. usage.Record now keeps Requested (req, the model id the client sent, a magpie alias or group as it came) and Served (served, the model the vendor's reply named, only when it named one); every usage.Append in the gateway fills them (the relayed and translated paths, Codex's backend, image draws, decide), and records written before load as they did, without them. The swap test the Routing page uses (a dated or pinned name is the same model) moved to usage.Swapped so the ledger marks the same rows. usage.Ledger gives the period's records newest first with each one's list-price cost, the agent it is counted under and whether the model was swapped, filtered by agent, failures alone and a search over the models, provider, account, session and effort; usage.WriteCSV writes them (time, agent, requested_model, provider, host, model, served_model, swapped, effort, input, output, cache write, cache read and reasoning tokens, cost_usd, duration_ms, ttft_ms, status, error, session, kind). The Usage page gets a third tab, Requests (请求), between Overview and Sessions, under the same period picker: an agent filter, All/Failed, a search field and Export CSV over a table of time, agent, requested model, provider · account, model sent, served model (amber with the Routing page's sentence when another model answered, plain on a dated name, a dash when the reply named none), effort, in, out, cache write, cache read, cost, duration and status, a failure's row marked by a red dot and bold code; the totals over it and 100 rows a page with Newer and Older. GET /api/usage/requests serves a page, POST /api/usage/requests/export saves all the filtered rows to Downloads (never over an earlier file) and opens the folder, GET /api/usage/requests.csv hands a browser the file in magpie web. The table scrolls sideways in its own box; at the window's narrowest the words after the head's cost give way so the three tabs fit without the page scrolling sideways. No border stripe; clicks move nothing (the pager held where it was). en and zh strings together. Tested with TestLedger (order, cost, swap, a dated name, an old record without the fields, each filter, the CSV exactly), TestServedModelRecorded and TestServedModelUnnamed (a request through a fake upstream answering with another model records Requested fake/sol, Model sol and the served name; one naming none leaves Served empty), TestUsageCSV (magpie usage --csv, a period, a stray argument refused), TestUsageLedgerRoutes (paging, totals, filters, export to Downloads twice) and a new internal/gui/tests/usage-ledger.test.cjs in Chromium and WebKit, en and zh, light and dark: the tabs and columns, the swap tag on the swapped row only, the 429 row, Older and Newer over 130 requests with the pager left where it was, the agent, Failed, search and period filters asking the server from the first page, Export CSV posting the filters with no page and saying where the file went, at 560 wide no tab scrolling the page sideways and the cost uncut, no left border. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... and the whole GUI suite (122 tests) pass. Not tried in the real window through Wails or against a live gateway's log.
2026-09-29 21:26:01 +08:00
usage: a Requests tab lists every request of the period, one row each, and exports them as CSV, from the window and from magpie usage --csv (Discord 范不着: a per-request ledger as sub2api keeps, to set beside the upstream's own bill) — the Usage page summed calls by agent and model, and the log did not keep the model the agent asked for or the one the reply said answered, so a single request could not be checked against a vendor's bill. usage.Record now keeps Requested (req, the model id the client sent, a magpie alias or group as it came) and Served (served, the model the vendor's reply named, only when it named one); every usage.Append in the gateway fills them (the relayed and translated paths, Codex's backend, image draws, decide), and records written before load as they did, without them. The swap test the Routing page uses (a dated or pinned name is the same model) moved to usage.Swapped so the ledger marks the same rows. usage.Ledger gives the period's records newest first with each one's list-price cost, the agent it is counted under and whether the model was swapped, filtered by agent, failures alone and a search over the models, provider, account, session and effort; usage.WriteCSV writes them (time, agent, requested_model, provider, host, model, served_model, swapped, effort, input, output, cache write, cache read and reasoning tokens, cost_usd, duration_ms, ttft_ms, status, error, session, kind). The Usage page gets a third tab, Requests (请求), between Overview and Sessions, under the same period picker: an agent filter, All/Failed, a search field and Export CSV over a table of time, agent, requested model, provider · account, model sent, served model (amber with the Routing page's sentence when another model answered, plain on a dated name, a dash when the reply named none), effort, in, out, cache write, cache read, cost, duration and status, a failure's row marked by a red dot and bold code; the totals over it and 100 rows a page with Newer and Older. GET /api/usage/requests serves a page, POST /api/usage/requests/export saves all the filtered rows to Downloads (never over an earlier file) and opens the folder, GET /api/usage/requests.csv hands a browser the file in magpie web. The table scrolls sideways in its own box; at the window's narrowest the words after the head's cost give way so the three tabs fit without the page scrolling sideways. No border stripe; clicks move nothing (the pager held where it was). en and zh strings together. Tested with TestLedger (order, cost, swap, a dated name, an old record without the fields, each filter, the CSV exactly), TestServedModelRecorded and TestServedModelUnnamed (a request through a fake upstream answering with another model records Requested fake/sol, Model sol and the served name; one naming none leaves Served empty), TestUsageCSV (magpie usage --csv, a period, a stray argument refused), TestUsageLedgerRoutes (paging, totals, filters, export to Downloads twice) and a new internal/gui/tests/usage-ledger.test.cjs in Chromium and WebKit, en and zh, light and dark: the tabs and columns, the swap tag on the swapped row only, the 429 row, Older and Newer over 130 requests with the pager left where it was, the agent, Failed, search and period filters asking the server from the first page, Export CSV posting the filters with no page and saying where the file went, at 560 wide no tab scrolling the page sideways and the cost uncut, no left border. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... and the whole GUI suite (122 tests) pass. Not tried in the real window through Wails or against a live gateway's log.
2026-09-29 21:26:01 +08:00