* gui: choose installed interface and code fonts and styles
Discover installed styles through DirectWrite, CoreText and Fontconfig. Keep choices per computer, apply them before paint and notify both desktop webviews after saving; defaults retain platform and Omarchy fallbacks.
Verified seven focused Go tests, including real Windows discovery (90 families, 276 styles), 14 font browser cases in Chromium/WebKit, 68 settings/zoom/Omarchy/web cases, go vet, Windows amd64/arm64 and Linux nogui builds. Against the original assets, the new UI test fails with Settings must offer an interface font picker (0 != 1). Full Windows suites have separately reproduced upstream failures; macOS/Linux native discovery is covered by the added CI step.
* gui: retain bundled Mac fonts and variable style precision
Only filter an undownloaded CoreText face when it is explicitly downloadable; bundled fonts are installed without being downloaded. Use resolved font names when descriptors omit them. Preserve fractional weights and nonnegative CSS widths for named variable styles.
* gui: restore the authored font cascade when returning to defaults
Apply family-only trait overrides only while a custom face is active. The browser regression catches code under a bold parent becoming 700 instead of its original 400. Keep the compatibility shim first, and apply boot fonts before visible content.
* gui: keep font picker review separate from CI integration
Leave the native test sources and manual verification command with the feature. Move the added workflow step to a follow-up so the font picker can take the normal review path requested on PR #1007.
On a MacBook the window had last been zoomed. magpie skipped a maximised
window when it saved the size, so it reopened at an older, nearly full
size: 1712×1083 at (8,34) on a 1728×1084 work area, neither full nor the
size it was given nor the default.
settings.WindowMaximised (this computer's own, KeepOwn) now records
whether the window was maximised when it settled; Window stays the size
it restores to (settle). A window not shown yet, let go, full screen or
minimised, or on Linux hidden, records nothing.
Reopened maximised: on the Mac and Windows placeMain maximises the made
window just before its first Show (on Windows once its page has come);
Wails' StartState is undone by the Mac's centring and shows a Windows
window before its page. The least size Maximise drops is put back. On
Linux makeMain sets StartState, since GTK can't be asked about a window
not mapped yet.
A size kept on a larger screen: Windows fits the window to the work area
and centres it, title bar in reach, and keeps the larger size for the
larger screen (fitted); the Mac fits and zooms it itself, and magpie
doesn't zoom it again, so it can still be restored.
TestSettle fails with upstream's rule (5 of 9 cases); TestOpenSize,
TestFitRoom, TestWindowMaximisedKeptAndOwn; the Mac's real build under a
sandbox HOME, 11 cases.
TestWarmAtEachAccountsOwn, TestWarmLookAtAnAccountsOwnTime and TestSetCodexWarmAt pass with -race -count=20, and the first two fail when an account's own time is ignored. TestSettingsSaveKeepsWhatItDoesNotSend covers the new field and fails without the keep in api.go. warm-at-own (two accounts: rows, Own posts 06:00 then 09:00, Off, back to Same, the page doesn't move, nothing cut, no <select>; one account: no rows) passes in Chromium and WebKit, en/zh/ja/de, 900px and 440px, and fails 8 of 9 with main's app.js. i18n-keys, gui-ja, gui-de, settings-groups, settings-sections, click-scroll, reset-reminder, usage-alert and codex-auto-review pass in both engines; go test ./internal/provider ./internal/settings ./internal/gui and vet pass.
* tui: show a ChatGPT account's credits beside its windows
The Usage page printed only the balance for an account that had one, so a
ChatGPT account holding credits lost its 5-hour and weekly windows there.
The balance now follows the windows, as on the app's cards and in
magpie quota.
* accounts: tell a ChatGPT account's credits
magpie accounts left out the credits an account holds, which magpie quota
and the Usage page show. Its rows now carry them, in the line and as
balance in --json.
* codex: a switch for whether an account spends its credits
A ChatGPT account holding credits keeps answering once a usage window is
used up: the vendor spends the credits, and a task goes on. That stays the
default. Each Codex account now has a say in it (Usage card and menu bar
panel: Use credits; magpie quota credits <account> on|off; settings'
codexNoCredits). Turned off, the gateway holds the account at 100% of a
window, as a usage cap holds it at its share, so the other accounts,
groups and fallbacks take the request and no credits are spent. With none
left the request gets a 429 naming what holds each account and how to go
on, unless the account spends its resets by itself and its week is used
up: then a reset is spent first, whether every candidate was held or the
last one tried was refused. A cap set on the account takes precedence.
A reset made now forgets the account's cached allowance, so neither a cap
nor this hold outlives the windows it started again. The switch is
routing's only: which account Codex is signed in to is unchanged.
The Routing page tells why such an account was left out, and magpie quota,
magpie accounts and the TUI mark credits not spent.
* usage: an auto-reset switch keeps by its first line
The auto-reset row centred its switch on its words, so the switch moved
when they rewrapped on a click, and it sat lower than the Use credits
switch beneath it. Both now keep the switch by the first line.
* gui tests: quota-curve expects the Use credits row to close a Codex account's section
The credits switch adds a row after the Auto-use one, so the account after
a Codex account follows the curve, the Auto-use row and then the Use
credits row.
Resolve deliberately accepts arbitrary model names under an existing provider.
The auto-review setting consequently accepted fake/missing and fake/, replacing
a valid reviewer with one that fails when Codex requests approval.
Validate against the served catalog at the settings endpoint. Keep model and
group choices, unlisted providers, whitespace trimming and clearing. Rejected
values leave the saved reviewer and model-list tag unchanged.
TestCodexAutoReviewSetting fails against the upstream API with fake/missing
and fake/ returning 200, and passes with the fix. Focused auto-review tests,
go vet ./..., the Windows build and Linux nogui build pass. The full Windows
suite has failures also reproduced with upstream production code; details
are recorded in the PR.
Codex's auto-review (approvals_reviewer = "auto_review") runs on the
auto_review_model_override of the conversation model's entry in its list,
else on codex-auto-review, else on the conversation's model at low effort,
so a conversation on an expensive model reviewed every approval on it.
settings.CodexAutoReview, picked from the app's menu (magpie's models and
groups, or Codex's own pick), is now put as auto_review_model_override in
every entry magpie hands Codex: magpie-models.json and a signed-in Codex's
/models, the ChatGPT backend's entries included. Unset, the list is as it
was (an override magpie left in Codex's cache is taken out, one OpenAI gives
kept). POST /api/settings/codex-auto-review refuses an id magpie doesn't
serve; settings refuse a value that is no model's id; the list's ETag
carries it so Codex asks again; another Settings save keeps it.
Tested end to end in a sandbox HOME with a real DeepSeek account and Codex
0.160.0 (codex exec, approval_policy on-request, approvals_reviewer
auto_review, read-only sandbox, a command asking for escalation): with the
setting deepseek/deepseek-flash the guardian request (kind guardian) went
to deepseek-flash while the conversation stayed on deepseek-v4-pro; unset,
magpie-models.json had no override and the review ran on deepseek-v4-pro.
Picking DeepSeek V4.1 Flash in the real web UI's Settings row wrote the
setting and rewrote magpie-models.json (4 entries with the override), and
the next codex exec reviewed on deepseek-flash. TestCodexAutoReviewEntries,
TestCodexAutoReviewChecked, TestCodexModelsAutoReview,
TestCodexAutoReviewSetting and codex-auto-review.test.cjs (Chromium and
WebKit, en and zh) are new and fail without the change; gateway-mode and
settings_keep cover the new row and field. go vet, go build (GOOS=linux
nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
Saving a routing group took about 2.4s on a machine with 20 providers and
95 models. The save is not slow: the state it answers with is. groupsState
asks every group for its members and every model for its facts, and the
held middleware let only GET share one build of the catalog
(provider.Hold), so each of those look-ups built it again — 20 builds in
one save here, each reading every provider's files and every agent's
sign-in.
The Routing page's own 25s trace long-poll is a GET, so it usually holds
the catalog and the save costs ~140ms. Whenever it is not in flight — the
first save after the page loads, or between two polls — the save falls
back to the full 2.4s.
A write shares the build now. It is safe because every write drops what is
held as it happens: provider.store ends in catalog.Touched, and
catalog.Forget is Changed (provider/hold.go), while settings.Save calls
catalog.Forget itself. So a save still answers with the state after the
write, never the one before it, and the request leaves nothing held for
the next. Reads are unchanged.
TestSaveSharesOneCatalogBuild counts the builds one save makes
(provider.AllBuilt: 20 before, 2 after) and checks the answer is the state
just written — the group saved, renamed, deleted. TestAgentWriteAnsweredAs
Written covers the one path a write does not drop the catalog on,
agent.Apply (it writes an agent's config and never calls
catalog.Touched): the field it wrote is in the answer that POST returns.
POST /api/groups/save, the same group of 6 members (5 of them groups),
same config, same machine: 2452, 2659, 2388, 2333, 2308ms before
(median 2388ms), 1650, 138, 127, 124, 137ms after (median 137ms; the first
is the cold start). go vet, go build and go build -tags nogui pass.
TestGatewayFixQuitsOnlyAMagpie and TestPurposeAssetMatchesUsage fail on
unmodified main here too (a running magpie on the port, a stale generated
catalog), so they are not this change's.
Co-authored-by: wizzy-yang <wizzy-yang@users.noreply.github.com>
Until now the only choice was 272K for every model or Full window for every model. The 272K default stays as it is (e950eb08: OpenAI runs its own 1M models at 272K in Codex, and Chen saw 70–90s to a first token at 550K).
What changes:
- A provider's editor has Compact at beside Context window and Max output, in the same form: "500k" for all of its models, "deepseek-v4-flash=272k" for one, and empty to follow Settings. A size at or above a model's window means the whole window. The values are kept in settings.ModelCompacts.
- Settings → Long conversations takes a size of its own beside Compact at / Full window (settings.CompactAt).
- `magpie model compact` takes a size such as 500k.
Order: the model's own value, then its provider's "*", then Settings. Full window turns off only the Settings value. A group uses the smallest value among its members.
Where it applies:
- Codex's model_catalog is told the threshold as context_window, with the whole window as max_context_window.
- Claude Code's CLAUDE_CODE_AUTO_COMPACT_WINDOW follows the main model. A Claude model now takes its provider's value when one is set.
- The Codex list tag changes when the Settings size or Full window changes.
Tests:
- New: TestModelCompactsSetFromTheEditor, TestSetCompactAt, TestCodexCatalogCompactAt and TestClaudeCompactAt.
- TestSettingsSaveKeepsWhatItDoesNotSend now covers the two new fields.
- compact-at.test.cjs (WebKit, en/zh) checks the editor field (shown, refused, chips, saved) and the Settings size (posted, hidden under Full window, no scroll).
- go vet ./..., go test -tags nogui of the root package, provider, codexcat, agent, gui, gateway, settings and catalog under a temp HOME, and GOOS=linux/windows builds all pass.
* refactor(agent): connect Aside as a native provider with verified settings
* fix(agent): offer explicit offline Aside model and disconnect actions
Offer Save for next start after an initial runtime read failure, using a
structured API error instead of saved runtime state. Keep refusals and
failed readbacks on the live error path.
Add a separate offline disconnect confirmation that restores the checked
file plan, clears restore records and removes the provider last. Bind the
confirmation to its preview revision and roll back ordinary write failures.
Cover offline API and browser flows, cancellation, stale plans, missing
CLI, restore metadata and native provider spelling. Keep state file-only.
thedavidweng asked to be told before an allowance resets, or a banked reset runs out, while it is still unused. magpie's notifications only said a window was nearly used up or a balance was low.
Settings › Usage now has a Reset reminder row: Off, 12 h, 24 h or 48 h before (settings.ResetReminder, 0 to 168 hours).
What it tells of, through the same watcher and notifications as the usage alerts (WatchQuotas, dueReminders):
- a window of a day or more (weekly, monthly) renewing within those hours with under 85% of it used. A 5-hour window renews too often to be news.
- an account's resets (Codex's, a GLM team plan's) expiring within those hours unused.
Each is told once per run, kept in usage-alerts.json as the alerts are. A failed or stale reading says nothing.
The notification reads, for example, 7 days: 55% left, renews tomorrow 14:30 — use it before then, and 2 resets unused, expiring tomorrow 14:30. zh, ja, de strings, there and in the row.
Turning it on asks the system's leave to notify and looks at once, as the alerts do.
Tests:
- TestDueReminders: once per run; a 5-hour window, one 90% used and one renewing later are left out; a longer lead takes the later one; resets told; a failed reading says nothing.
- TestReminderText (en, zh, ja, de).
- reset-reminder.test.cjs (WebKit, en and zh): the row, a pick saves it, another save keeps it, no click moves the page. It fails 2/2 on the old assets. usage-alert.test.cjs passes.
- go vet passes. The darwin, GOOS=linux nogui and GOOS=windows builds pass. go test -tags nogui -count=1 ./... passes under a temp HOME.
Before this, only MAGPIE_ADDR could move the gateway, and the app on a Mac or one started from the Start menu has no easy way to set an env var.
The setting:
- Settings has a Gateway port field. Any port from 1024 to 65535 can be typed, then applied with Apply or Enter.
- The port is saved in settings.json and kept on this computer (KeepOwn), so a backup or WebDAV doesn't carry it across.
- MAGPIE_ADDR, where it is set, still comes first. The field is then off and says so.
- A port out of range is refused in the page.
- A port another program listens on is refused: "port N is in use by another program: pick another one". The gateway stays where it was.
- With another magpie serving the gateway, the field says to set the port there.
On a change:
- The listener moves live (Relisten listens on the new port before closing the old one).
- When the old port had been taken by another program and nothing served, the gateway starts on the new one.
- Every agent connected to magpie has its config rewritten with the new URL (agent.OnGateway before the move, agent.Rewire after):
- each one whose wiring is now "unwired" is set again;
- Codex signed in with ChatGPT is joined again and keeps its own model;
- Cursor Private Inference's variables and dsh's routes follow.
- What each agent had before magpie (the stash) is kept through this. Before, setting an agent again on the old URL would have written magpie's old URL and token over it.
- The page says how many agents moved.
What follows the saved port (through gateway.Addr()/URL() and settings.GatewayAddr):
- the CLI and healthcheck;
- Running();
- LAN sharing (Port);
- the drift check for the installed magpie, from beside a MAGPIE_ADDR one;
- importapps' "is this entry magpie's own" check.
zh, ja and de strings.
Tests, all failing on the old code:
- TestPortMovesEveryAgent: every agent with a config file is connected at 3591 and moved to 3592. No file keeps 3591, and none is off afterwards.
- TestPortMovesJoinedCodex.
- TestPortLeavesAnUnwiredAgent.
- TestPortKeepsWhatAnAgentHadBefore: the relay URL and own token come back on disconnect.
- TestDriftInstalledMagpieOnItsPort.
- TestSetPortMovesTheGateway, through the API: taken, out of range, moved, the old port silent, POST /api/settings keeps it, MAGPIE_ADDR refused.
- TestSetPortServesWhenTheOldOneWasTaken.
- TestGatewayURLOnSettingsPort.
- gateway-port.test.cjs on WebKit, en and zh:
- out of range, taken (translated), applied by Enter, the agents counted, the new URL shown;
- the MAGPIE_ADDR state;
- no select, no left border, and the page never scrolls (the row is swapped whole, or WebKit laid the list out empty and the view jumped).
Sandbox run (own HOME, settings port 3591, magpie web on 3593):
- Claude Code (over a relay with its own token) and Codex were connected at 3591.
- POST /api/settings/port:
- 3594, held by another program: refused, in use;
- 80: refused;
- 3592: moved [Claude Code, Codex].
- Both configs then say 3592. The gateway answers on 3592 and not on 3591, and `magpie healthcheck` exits 0.
- `magpie claude default` gave back https://relay.example and own-token.
lan-docker, settings-sections, settings-groups, click-scroll, gateway-caller-keys, gui-ja, gui-de and webdav-settings-refresh pass on WebKit.
go vet, the builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
#825 (cjyrainbow): Settings → Web search's 代搜供应商 offered only DeepSeek though they have many providers. Only a provider that searches the web by itself can search for another model — Claude, Codex and Grok accounts; the APIs of Anthropic, OpenAI, DeepSeek, xAI, Zhipu and OpenRouter; Gemini on a Google sign-in; a Kimi Code plan — so the list is right, but the row never said so. It now names the providers on that are left out (searchLeftOut; past six, the first five and how many more) and says that for their models the search APIs below search. zh, ja, de strings. searcher-pick.test.cjs (WebKit, en and zh) checks the line and the cut-short list; it fails on the old code.
#824 (babyjiang7): Alma reads personal skills from ~/.config/alma/skills (its skills service, in Alma.app's bundle: personalSkillsPath, beside ~/.claude/skills, ~/.codex/skills and ~/.agents/skills), so the library now gives it skills there, Claude Code's, Codex's and the shared folder's counted as its too. It still has no user-wide instructions or MCP file of the kind magpie writes, and says so. TestAgentsSkillsFolders (a skill given to Alma lands in ~/.config/alma/skills) and TestTakesRefusesAlma fail on the old code.
go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn and TestClaudeSignedOut flaked, pass alone).
Each saved search API with a key gets Show and Copy next to Remove. Show asks the new POST /api/settings/search-key for the key (served on loopback, as provider/key is) and puts it in the row's line in place of the masked one, selectable; Hide puts the masked one back. Copy copies the key without showing it. The settings still list keys masked. search-api.test.cjs checks Show/Hide/Copy (fails on the old app.js) and TestSearchKeyShown the endpoint. GUI: search-api, searcher-pick, click-scroll pass on WebKit; go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
Search: Code Assist runs googleSearch for Gemini, but Antigravity turns it down beside function tools (400 "Please enable tool_config.include_server_side_tool_invocations to use Built-in tools with Function calling", tried with that flag in toolConfig too), so searchesFor (searchesItself for one request) says a Google sign-in's Gemini searches by itself for a request without tools; buildCodeAssistSent then sends tools [{"googleSearch":{}}], and codeAssistDecoder reads the candidate's groundingMetadata (webSearchQueries, groundingChunks[].web) as a search the client is told of (Anthropic's server_tool_use + web_search_tool_result), once. A request with tools — every agent's — is given magpie's search tool as before, and its searches go to the conversation's own account first (ownSearcher, as a Kimi Code plan's do): searchWith on its Gemini without tools, so googleSearch runs there and no other subscription is spent. A Google sign-in is also a searcher for other models, last of those magpie picks by itself (rank 5; relays named by hand move to 6), and is offered in Settings → 代搜供应商, with its Gemini models only; the row says such sign-ins search for their own models first. smallModel took every Gemini for a small model ("gemini" has "mini" in it), so the first listed, gemini-pro-agent, searched; it now picks a Flash.
Images: Drawers lists the image models a Google sign-in has (gemini-3.1-flash-image), and draw asks them on Code Assist through the account (drawCodeAssist, askTranslated with the conversation's envelope); buildCodeAssistSent asks an image model for responseModalities ["TEXT","IMAGE"], which a chat with one now gets too. AutoDrawer doesn't pick them by itself (no known price), so nobody's automatic drawer changes.
With the owner's real Antigravity account (a copy of the config, a sandbox gateway): a web_search with no other tool on gemini-3.8-flash-low came back with Google's grounding as web_search_tool_result; with a bash tool beside it the model called magpie's search, which went to antigravity/gemini-3-flash (not Codex) and gave pages; /v1/images/generations with antigravity/gemini-3.1-flash-image drew a 1024x1024 JPEG. TestCodeAssistSearchesWithoutTools, TestCodeAssistDecodesGrounding, TestCodeAssistImageModelAsksForImages and TestSmallModelOfGemini are new; searcher-pick.test.cjs checks the note. go vet, builds (darwin, GOOS=linux, GOOS=windows), go test -tags nogui -count=1 ./... under a temp HOME and the searcher GUI tests (webkit) pass. Not tried with a Gemini CLI sign-in, which takes the same path.
Now each row in a field that mixes the two carries a tag: "via magpie" (经 magpie) on magpie's, "direct, not via magpie" (直连,不经 magpie) on the agent's own, and "off magpie" (不经 magpie) on Default where picking it takes the agent off; the note no longer ends in "via magpie", so it is said once and never cut. A pick in the field an agent is connected through that takes it from one of magpie's models off magpie — one of its own models, or Default — (leavesMagpie) opens a confirm as the switch's off does (askLeave, the disconnect-ask dialog, Cancel focused): it says what the pick does, that 「接入」 goes off (and the row goes under Not set up when other agents are on magpie), and that running copies take it once reopened. Default's offers Disconnect and restore beside Use default, the two not being the same: Default leaves the agent as installed (magpie's endpoint and models out, its own default model) and neither puts back what it had before magpie nor keeps the models for the next switch-on, which Disconnect does. Confirmed, it is written as before (setPick, the old commit body), Default saying "no longer goes through magpie · on its own default".
Switched on, the status says what model the agent was started on and why: agent.ConnectHow returns a Connection (how: kept, again, joined, magpie, same, alike, default, first; field; value) from the rule Connect follows (reconnect's memory; Codex on ChatGPT joined, its own last pick kept and magpie's models in its /model; else the model it was on through magpie — its own account, a subscription, a key — or its alias family, or with none set its catalog's first; else the first on its own account, the first subscription, the first), POST /api/agents/connect/{id} returns it as state.connected, and app.js says e.g. "Claude Code is connected to magpie · on Claude Sonnet 5.5 through magpie: it had no model set, so the first of its own". Connect is ConnectHow without the answer. New strings have their Chinese.
TestConnectSaysHow (alike for {"model":"opus"}, kept when connected, again after a disconnect, first for a model magpie doesn't serve) and agent-leave-magpie.test.cjs (the tags in a long-noted pop, Opus asked with nothing posted until Use, Cancel leaving it connected, Default's ask offering Disconnect and restore, a pick within magpie's and one connecting an unconnected agent not asked, the switch's how in the status, no click moving the page, en and zh) fail on the old code (ConnectHow undefined; no .badge.path) and pass; claude-picker-fold's note expectations drop "· via magpie" and it checks the tags. go vet, builds (nogui, GOOS=windows, GOOS=linux nogui), go test -tags nogui ./internal/agent ./internal/gui under a temp HOME, and the agent-*, claude-*, click-scroll and show-unset-agent GUI tests in WebKit pass; the rest of the suite fails as on HEAD (cli-update's tight row and others), and Chromium doesn't start in this sandbox. Not tried with the reporter's accounts or in the desktop window.
* gui: searchers show provider model names
Settings web search reads the names given in Providers from provider.Served, the same source as image recognition, so Automatic and a named searcher show those names instead of bare model IDs. Tests cover both.
* gui: searcher-pick's zh case names the searcher by its model name too, as the en one does — the turned-off note reads 没有用 OpenAI · GPT-5 Mini Named now that the small model goes by its name
---------
Co-authored-by: hazukisendai <251835060+hazukisendai@users.noreply.github.com>
Co-authored-by: yetone <yetoneful@gmail.com>
Every agent row keeps its 接入 switch and now has its model picker beside it. Picking a magpie model on an agent that isn't connected connects it first (agent.Pick, used by /api/set), so one pick sets the agent up; disconnecting still puts back what the agent had. The intro line says both ways.
An opened row shook: the connected row centred its lines in its 56px min-height, and the 3px row-gap couldn't be folded away, so opening moved the name and the switch and changed the space under them. The first line now has its own height and isn't centred, the row-gap is 0 with the spacing in margins, and the opened part starts where the shut row ended (56/56 at 980px, 90/90 at 560px; the name at 8px and the switch at 19px open and shut). Opening and closing animate the part's height, padding, margin and border on iOS's sheet curve cubic-bezier(.32,.72,0,1), 460 ms open and 340 ms shut, and what's in it fades in from 6px above; a row already open slides shut before another opens. With reduced motion it opens at once.
Gemini CLI's .env made by magpie for its key was left holding a newline after a disconnect (main too): DelEnvFile now removes a dotenv file once the deleted keys were all it held.
TestPickConnectsFirst, the only.env case in TestEnvFile, agent-start-pick and agent-expand-steady (name and switch unmoved, the opened part's top at the shut row's end, at least 3 heights between on the way open and shut, growing/shrinking only, chromium and webkit at 980 and 560px) fail on the old code and pass. Tests that open rows wait for the slide. In a sandbox HOME with real accounts: Claude Code and Gemini CLI picked while not connected answer PONG and disconnect back to what they had; Goose's pick → disconnect round trip is byte-identical (goose itself stops at a Linear MCP OAuth, so no request). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui for edit, agent and gui under a temp HOME pass; GUI tests that fail here fail the same on 82340f41 (account-arrange, cli-update, mobile-web, provider-levels-scroll, provider-proxy, redact-rule-row, routing-compact, provider-detect-models, plugin-move).
- The opened row follows the boards: what to do in the agent, running copies with the old list, Claude Code's tiers (each with its own effort beside its model again, #536), the model list by whose models they are with what's hidden, the model new sessions start on, what connecting costs (Gemini's Google sign-in), and the files put back on disconnect, shown line by line.
- Disconnecting asks first and shows the lines it puts back. Secrets are masked, and a config of tens of thousands of lines diffs in a fraction of a second.
- Connecting prefers the agent's own subscription when magpie serves it (Claude, Codex).
- The dry run goes through proc.
- Every model hidden still keeps the row connected, with the way back to the list (#356).
- Routing groups are named in Chinese.
- Phone widths: the pickers and squares wrap inside the opened row, and the name stays beside its logo.
GUI tests moved to the new rows: connected fixtures carry refs and open the row from its link. tier-effort and mobile-web each found a real gap and now guard the fix.
Tried in a sandbox HOME with the owner's real accounts:
- Claude Code: connected, a request answered, and disconnect restored the settings exactly.
- OpenCode: a request answered.
- Gemini CLI: connected.
- Codex: connected.
- Model list picks were saved.
go vet, builds (darwin, GOOS=windows, GOOS=linux -tags nogui) and go test -tags nogui ./... under a temp HOME pass. The agent GUI tests pass except cli-update's tight-row case, menu-scroll and old-webkit, which fail on main too.
* gateway: let a searching relay be named
A provider marked as searching by itself is still not picked automatically, so a relay quota is not spent for other models, but Settings can now name it as the searcher. The row and tests say both halves of that rule.
* gateway: keep named search relays out of fallback
Search ranking is automatic and fallback ranking again, while Settings additionally accepts a relay said to search. Manual-only choices come last and modelSearch skips them, so a failed automatic search never spends a relay quota; a provider on a searching host keeps its automatic rank even when its box is ticked.
---------
Co-authored-by: hazukisendai <251835060+hazukisendai@users.noreply.github.com>
A card's logo is its handle. A grip shows by it only while the pointer is on the card, as on Agents and Providers. The card is dragged across the grid: dragCards is dragRows in two directions, the slot nearest its middle is where it goes, and the cards between move a slot over. Alt+arrows move it from the keyboard and keep the focus on it. The order is settings usageOrder, by provider id, saved by POST /api/usage/arrange. It is apart from the Providers order, which is also the order providers are tried in. POST /api/settings keeps it. A card it doesn't name follows in magpie's own order, and an order naming one not on the page now keeps it after them. The tray panel's Usage tab follows the same order.
usage-arrange.test.cjs covers the hover-only grip, a drag across the grid, Alt+arrows, a click that moves nothing, the order after a reload, and the panel. It passes in Chromium and WebKit, en and zh. TestSettingsSaveKeepsWhatItDoesNotSend covers usageOrder. go test ./internal/gui ./internal/settings under a temp HOME, go vet, and builds (darwin, GOOS=linux, GOOS=windows) pass. Also tried on a sandbox magpie web with faked quotas: the drag, the keyboard and a reload kept the order in WebKit and Chromium. The other Usage/panel UI tests pass, except credit-days.test.cjs, which fails about 1 run in 4 on main too.