20 Commits
Author SHA1 Message Date
yetone 9e665491ec context: Tuned for you — each agent's settings worked out from the user's own calls, for the fewest tokens, put in on a click and undone on another
The Context tab now replays the period's calls of each agent against the
settings that decide what they cost, and shows, per agent and per model,
the value that would have used the fewest tokens for how this user works:
Claude Code's auto-compact window per model (modelSettings.<model>.
autoCompactWindow), its main and subagent prompt cache lifetimes
(promptCacheTtl, subagentPromptCacheTtl: 5m or 1h), and Codex's
model_auto_compact_token_limit. Each row says where the value comes from
now (the agent's default, the user's own setting, one magpie set, or a
variable that comes first and locks it), why (how fast conversations grow,
what a compaction leaves, how long the user pauses), the cost curve, and
the saving. Nothing is written until "Use …" is clicked; what was there is
stashed and Undo puts it back, unless the user changed it since. A window
the user set past the model's own is shown capped, as Claude Code caps it
(min(setting, model window)), so no saving is claimed that wouldn't happen.
Advice under a 3% saving is "already the leanest".

On the owner's 7 days (88K Claude Code calls, 956 Codex): opus keeps 200K
(their 500K is capped to it), fable 5.1 200K → 160K saves 5% (2.56M
tokens), both cache lifetimes and Codex's 272K are already the leanest.

Run: go vet ./... and go test ./... pass (internal/tune, internal/agent
TestTune*: user-set values kept, env locks, undo after a user edit, TOML
key order). context-tune.test.cjs passes in Chromium and WebKit, en/zh/
zh-TW/ja/de, at 360 and 1100px (22); context-window, context-scroll-end
pass. Full GUI suite: library-servers-bulk and market-have failed once
under load and pass alone; market-have fails on origin/main 494dedcf too
(1 of 8 runs). Claude Code 2.1.296 in a sandbox config dir shows
"Auto-compact window for Fable 5.1: 160k tokens (from settings)" for the
written modelSettings key. The real-data advice was read from the owner's
own ~/.claude and ~/.codex, nothing written.
2026-10-10 15:05:53 +08:00
木子不是木子狸 c9b8263378 sessions, gateway: Sessions lists gateway sessions, and can keep their text on this computer when turned on (#1355, mu-zi-lee)
What changed: the Sessions page lists sessions seen only through the
gateway (from the usage ledger, read-only, a native session of the same id
wins), and shows in gateway mode. A Sessions switch, off by default and
confirmed before it turns on, records each gateway exchange's text under
the config dir (scrubbed, at most 7 days and 256 MiB, cleaned hourly).
Settings sync and restore can't turn it on; POST /api/sessions/recording
turns it on or off and can clear what was kept.

Why: users of magpie as a gateway (gateway mode, LAN clients) had no way
to see their conversations; the owner chose this design on the PR.

Squashed from PR head 6a343ba1 onto origin/main. Conflicts: gui-shell.md
(kept main's placeMenu text, took the PR's Sessions in gateway mode) and
settings_keep_test.go (main's fields plus GatewayConversations).

Run: go build ./..., go vet on gateway, gui, sessions, settings, usage;
go test -race -timeout 900s on those five packages: all ok.
2026-10-10 04:24:18 +08:00
yetone b4bdd59f72 gui: a long app menu stays in the window and scrolls inside itself, so the last removed group can be brought back (#1437, Hipple)
With 28 found groups removed, Routing's "28 found groups removed" menu ran
off the bottom of a ~717px WebView2 window and couldn't be scrolled.
openRowMenu had no height cap (max-height: none, and .pop clips with
overflow: hidden), it only flipped up when the whole menu fit above, and any
scroll, even one inside the menu, closed it.

What changed:
- placeMenu (app.js) places every app menu: under its anchor, over it when
  the window runs out below, and one that fits neither way takes the larger
  room with its height held to it. openProtoMenu's placement moved into it
  unchanged; openRowMenu (agent rows, model chips, Routing's removed groups
  and its other row menus) and openSessCombo (the Sessions/Usage filters,
  which could run off the bottom the same way) now use it too.
- .row-menu scrolls inside itself (overflow-y: auto, overscroll-behavior:
  contain), and openRowMenu closes only on a scroll outside the menu, as
  openProtoMenu and openSessCombo already did.
- docs/subsystems/gui-shell.md names placeMenu.

Verified:
- New removed-groups-menu-scroll.test.cjs: 30 removed groups, 1100x700 and
  560x700, en and zh, Chromium and WebKit, 8/8 pass. It wheels the page to
  the line, clicks it where it is, checks the menu is inside the window and
  scrolls, wheels the menu until the last entry shows inside it, clicks it
  (posts groups/show auto-gone-30), and checks the page never scrolled and
  has no sideways scroll.
- Broken by hand three ways, each red in every case: the old app.js/app.css
  ("the menu stays in the window": bottom 1494 of 700), the old
  scroll-closes-menu handler ("scrolling the menu doesn't close it"), and no
  overflow-y on .row-menu ("the last entry is scrolled into view inside the
  menu").
- The 63 GUI test files that open app menus or guard click-scroll, both
  engines: 595 pass, 1 fail: search-api.test.cjs webkit zh, at page load
  (the row is redrawn between waitFor and scrollIntoViewIfNeeded) before any
  menu opens; it passes 5/5 alone. Not run: the rest of the GUI suite, and
  gui-zh-tw/ja/de (no new strings).
- gofmt (no Go changed), go vet ./internal/gui (darwin, GOOS=windows),
  GOOS=linux and GOOS=windows go vet -tags nogui ./internal/gui, and
  go test -tags nogui ./... under a sandbox HOME: all pass.
- Not tried in a real Windows WebView2 window.
2026-10-10 01:26:57 +08:00
yetone 3cd0b5f78d gui: lightweight mode no longer lets go of a main window that is open under another app's, so Command-Tab back to magpie finds it there (#1381, ttmouse)
Cause: lightweight mode (#580) let go of the main window once two of its
30 s looks found it "not shown", and it asked Wails' IsVisible, which on
the Mac is the window's occlusion state. A window the user had
Command-Tabbed away from, covered by the other app's window, counted as
closed: 30-60 s later its webview was dropped and the window closed, and
Command-Tab back to magpie found a blank shell or no window at all. Hidden
with magpie (Command-H) it went the same way, since hiding orders every
window out.

Change: windowUp says whether a window is up as the user sees it:
ordered in on any Space, covered or not, minimised, or open as magpie was
hidden (watchHide records those on NSApplicationWillHideNotification).
Only a window magpie closed itself is down. lighten (through lightShown)
and mainShown use it, so the Dock-while-open setting and a restart to
update also keep a covered window as open. Elsewhere windowUp is
IsVisible as before (visible there covers covered and minimised).

Run:
- Reproduced with a sandbox build of origin/main (own HOME and
  CFFIXED_USER_HOME, port 3481, dock+lightweight on): with the open
  window covered by a full-screen window of a helper process for 70 s the
  log said "the main window's webview let go" and the window was a blank
  white shell. With this change, the same run let go only the closed
  panel and the window kept its page (screenshot of the window).
- Activation itself was checked by handing activation from the helper to
  magpie (NSRunningApplication activateFromApplication): with the window
  there it came forward. A real Command-Tab keystroke was not sent.
- New TestCoveredWindowStaysUp (AppKit, own process): open, covered by
  another window (until IsVisible says false), hidden with the app,
  minimised -> up; closed by magpie, also with the app hidden -> down.
  With windowUp put back to IsVisible it fails: "open: the window counts
  as up=false, want true"; without the hidden clause: "hidden with
  magpie: the window counts as up=false, want true". Passes -count=10.
- gofmt -l; go vet ./internal/gui; GOOS=linux go vet -tags nogui and
  GOOS=windows go vet (with and without nogui) ./internal/gui; go build,
  GOOS=linux go build -tags nogui, GOOS=windows go build;
  go test ./internal/gui and go test -tags nogui ./internal/gui under a
  temp HOME: ok.
2026-10-09 12:59:29 +08:00
yetone 2018882e47 davsync, gui: a server file that isn't a backup is said as what it is, and can be replaced by this computer's setup (JasonLeeForOnly on Discord)
JasonLeeForOnly's WebDAV sync failed with only "not a magpie backup" for
several versions, and nothing on the page got past it. A WebDAV GET of the
backup can come back as a body that isn't the file in several ways; each is
now reproduced with an httptest server that answers the way the real one
does:

Unpacked (WebDAV and S3): gzip with or without Content-Encoding (a proxy
that drops the header), x-gzip, zstd with or without the header, deflate
(zlib or raw), and a header that says gzip over a plain body. Bodies are
told by magic number; a backup is JSON, so either magic is never the file.
Brotli (br) can't be unpacked and is said as itself, with what to turn off.

Said, with no upload offered: an HTML page answered with 200 (Nextcloud
login after a redirect, Cloudflare Access, an AList/OpenList web app, an
rclone directory listing, a captive portal), with its <title> and the
host/path a redirect ended at (query, fragment and user dropped), and
where the WebDAV address is for 坚果云 and Nextcloud; an XML answer, by its
root element.

Said, with Upload as the way out: an empty (0-byte) file, a backup cut
short or damaged (the automatic rebuild is unchanged and still goes first
when its rules allow), another app's file ("format": "…"), other JSON,
and any other file by its content type. Messages carry the size, kind and a
page title only, never the file's contents.

backup.Open now tells ErrNotBackup (doesn't begin like a backup) from
ErrCorrupt (begins like one, cut short). The status carries the kind as
serverFile; Settings › Sync and backup says it in the reader's language,
wraps it whole, and shows "Upload this computer's setup" only when the file
may be replaced. davsync.Upload (POST /api/davsync/upload, `magpie webdav
upload`, `magpie s3 upload`) asks first in the GUI, re-reads the file,
refuses a backup this computer opens, one sealed with another passphrase,
a newer magpie's, a page and an XML answer, keeps the server's copy in the
sync folder (0600, newest 5) and writes nothing if that fails, then writes
with If-Match on the version read. New strings in en, zh, zh-TW, ja, de.

Verified: internal/davsync/not_backup_test.go (TestReadCompressedBackup,
TestReadBrotliSaid, TestServerAnswerSaid, TestCutBackupSaidAndUploaded,
TestS3ReadCompressedObject; Upload-only parts stripped to compile) fails
on e4a90aa6 for every compressed case but "gzip said", Brotli, every
server-answer case, the cut case and S3 gzip, and passes here. Dropping
the Replace check in Upload turns the page/XML cases red ("Upload wrote
over a page"). gofmt -l clean; go vet darwin, GOOS=linux and GOOS=windows
-tags nogui on ., davsync, backup, gui; full go test -tags nogui ./... ok;
-race on davsync and backup ok. GUI: sync-not-backup.test.cjs (en, zh,
zh-TW, ja, de; 900 and 440px), sync-*, s3-sync, gui-de, gui-ja, gui-zh-tw,
click-scroll in Chromium and WebKit, 81 pass. Not tried against a real
WebDAV server or on the Windows box; the reporter's server is unknown.
2026-10-09 12:10:04 +08:00
yetone 55544f09f6 routing, gui: a provider's accounts and a group in the group fold on the Routing page, long ones folded at first, what the reader picks kept (Aiirobyte on Discord)
Aiirobyte: a routing group of many accounts took so much of the page that
editing the groups below it was a long scroll. The stage folded only runs
of three or more API keys (#1319).

Now:
- Three or more accounts of one provider side by side for one model fold
  into one row the way keys do: the count, the lit one and what it does,
  how many are available and resting. A request to a folded account flies
  to the fold.
- A group in the group's heading folds from a click, Enter or Space (it is
  a button now, with a chevron). Folded, it says "{n} on" and what its
  seats do together; requests under it fly to the heading and a failure
  tags it.
- Accounts and keys sums a provider's accounts up in one row too.
- Keys start folded as before. Accounts and group headings start open
  under six, folded from six. What the reader opens is kept in
  magpie.routingKeysOpen as before, what they fold in the new
  magpie.routingFolded, so both directions survive a reload.
- Redraws go through steady(), so a click keeps the page where it is.
- Strings in en, zh, zh-TW, ja, de. Display only: the gateway's order and
  choice don't change.

routing-account-order now opens the relay's key fold by :not(.open),
since the account folds there start open.

Run: new internal/gui/tests/routing-group-fold.test.cjs (defaults, click
and Enter in place with the page not moving, kept across reloads both
ways, a live request to a folded group heading with its tag, Accounts and
keys folds, 420px fit) in Chromium and WebKit, en/zh/zh-TW/ja/de, 40/40.
On origin/main's assets it fails (no account fold, heading not foldable).
All routing-* tests, panel-routing, click-scroll, gui-ja, gui-de,
gui-zh-tw and i18n-keys: 255/255 in Chromium and 255/255 in WebKit. Not
tried in the real window with a live gateway.
2026-10-09 11:10:11 +08:00
yetone 8db24fd62f gui: Routing folds three or more of a provider's keys for one model into one row that shows the count and their state, and opens in place to each key (#1319, tkhs101)
53 agnes-3.0-flash keys were 53 near-identical stage rows and 53 rows
under Accounts and keys. Now three or more keys of one provider standing
together for one model (same fixed effort, fallback and group-in-group
heading) fold into one stage row: "Agnes 53 keys agnes-3.0-flash", then
the lit key and what it does, "51 available · 2 resting". A request on a
folded key flies to the fold; a key failing and the next answering show
there (its 429 tag too). A click, Enter or Space opens it in place: each
key keeps its own row, state and wire, drawn from the fold. Accounts and
keys folds the provider's keys into one row with their summed tally and
models, and opens the same way. Open folds are kept per reader in
localStorage magpie.routingKeysOpen. Two keys stay two rows; one key is
as before. Display only: the gateway's order and choice don't change.
New strings in zh, zh-TW, ja and de. docs/subsystems/gui-shell.md says how.

routing-account-order's relay has three keys, so it now opens their fold
to read their order.

Run: routing-keys-fold.test.cjs (32: folded, open/persist/Enter, live
updates folded and open, Accounts and keys, 440px; Chromium and WebKit;
en, zh, ja, de) — fails on main (no fold). All routing-*.test.cjs,
panel-routing, click-scroll, fold-show-grip, gui-ja, gui-de, gui-zh-tw,
i18n-keys: 409 pass. Every other test that opens Routing (29 files, 152
tests) passes but purpose-filter's WebKit "returning focus does not scroll
the page", which fails the same alone on origin/main (off by 1px).
go vet -tags nogui ./internal/gui (darwin, GOOS=linux, GOOS=windows).
2026-10-08 22:20:22 +08:00
yetone d226f87e4c gui: on KDE Plasma's Wayland session the quick panel has no KWin title bar or frame, as the main window has none (#1283)
GTK 3, which the Linux builds link, announces server-side decorations
over org_kde_kwin_server_decoration for every window that isn't
client-decorated, an undecorated (frameless) one too (gtkwindow.c
realize: announce_ssd), and KWin then draws its title bar on it. The main
window escapes it through plainTitlebar's hidden titlebar widget. The
panel now announces client-side decorations after each realize
(ownFrame: gdk_wayland_window_announce_csd), at start and when
lightweight mode makes it again. GTK 4 already asks for none, X11 and
compositors without the protocol never see it.

Run: on real KWin 6.3.6 Wayland (kwin_wayland --virtual with Breeze,
Debian trixie arm64 container, -tags gtk3 test binary): WAYLAND_DEBUG
shows request_mode(1) and KWin answers mode(1); a KWin script reads the
panel's frame 440x520 = its client 440x520. With ownFrame skipped:
request_mode(2), frame 442x557 over client 440x520 (KWin's title bar
and borders, the reporter's picture). go vet -tags gtk3 ./internal/gui
ok there; macOS go vet/go build ok; GOOS=linux/windows go vet -tags nogui
./internal/gui ok. Not tried: the reporter's Manjaro Plasma session, a
GTK 4 build, GNOME/Hyprland (no KDE protocol there, so unchanged).
2026-10-08 18:16:03 +08:00
yetone 23cd1bb609 gui: closing the quick panel from its title bar (or Alt+F4, a window manager's close key) hides it as Escape does, so the tray icon opens it again; it was closed for good and the icon did nothing until a restart (#1283, KDE Wayland)
The system's close reaches Wails as WindowClosing, which destroyed the
panel while h.panel and the tray still held it. makePanel now hooks
WindowClosing: the panel cancels and hides; one lightweight mode let go
(h.panel no longer it) still closes.

Run: TestPanelCloseHidesIt (AppKit, new) passes; with the hook's check
forced to let the close through it fails "panel close: the panel closed
for good; the tray icon can't open it again". On real KWin 6.3.6 Wayland
(kwin_wayland --virtual, Debian trixie container, GTK 3 build) a KWin
script closeWindow() on the panel (what its title bar X does): with the
hook alive=true and a second Show is visible; without it alive=false and
Show shows nothing. go test ./internal/gui ok (darwin, GUI tag); go vet,
go build ok; GOOS=linux/windows go vet -tags nogui ./internal/gui ok,
GOOS=windows go vet ./internal/gui ok. go test -tags nogui ./...: all ok
but internal/agent (11m timeout) and internal/plugin
TestHostRunsAsMagpieBun under load average ~110; neither imports
internal/gui, and TestHostRunsAsMagpieBun passes alone. Not tried: the
reporter's own Manjaro Plasma session; Windows' Alt+F4 on the box.
2026-10-08 18:16:03 +08:00
Zhu Wang f00ed2901c routing: filter by several purposes with checkboxes (#1247)
* routing: filter by several purposes with checkboxes

Ticking a purpose immediately shows the union of the selected purposes while keeping the menu open. All purposes, Clear filter and removing the last selection restore every purpose. Counts, the story and history follow the same list.

Live menus ignore renderer-induced scrolling while deliberate outside scrolling still dismisses them. Update the GUI shell reference and browser coverage for multi-purpose counts, keyboard toggling, history, aliases and narrow windows in five languages.

Verified 115 isolated Playwright checks in Chromium and WebKit, JavaScript syntax and git diff --check. The new checkbox case fails against the old assets with 0 checkbox choices instead of 5; restoring the old menu scroll handler separately detaches the menu during selection in WebKit.

* routing: All purposes returns keyboard focus to its button

Address the keyboard-focus regression reported in PR #1247. Choosing All
purposes closes its menu and focuses the purpose button without scrolling.
Unticking the last purpose keeps the live menu and its checkbox focused.

The expanded checkbox test fails without the focus restoration in Chromium
and WebKit: All purposes returns keyboard focus to the purpose button
(false !== true). With the fix, 162 isolated Playwright checks pass across
both engines, including five locales, narrow widths, purpose state,
history, request/session grouping, Usage and scroll/menu regressions.
JavaScript syntax and git diff --check pass. The full GUI/Go suites and the
packaged desktop app were not run.
2026-10-08 16:02:17 +08:00
yetone 586f2bce8f context: magpie inspects the context window of every request, session and agent
The gateway reads each request's prompt into its parts (system prompt,
tools and MCP servers, memory, files, tool results, chat), measured
against the model's window and calibrated to the tokens the vendor
counted. Routing shows a request's window as a card: a 400-cell grid by
part, a hover naming what is largest in a cell, and the contents by part.
The Usage page's Context tab scores each agent out of 100 (cache reuse,
how lean it starts, how fast it grows, how reliably it answers), tags it
(lean, cache-friendly, mcp-heavy, compacts, near-full…), and draws each
session's growth with its latest request's card.

Older Safari (15) gets media-query and solid-colour fallbacks for the
card's container queries and color-mix.
2026-10-08 05:36:17 +08:00
yetone aff4f9f22c gui, tray, alerts, settings: magpie speaks Traditional Chinese (繁體中文) with Taiwan wording, picked on its own for a zh-TW, zh-HK or zh-Hant system
Settings → Language offers 繁體中文 beside 简体中文. On System, a
browser or OS locale of zh-TW, zh-HK, zh-MO or zh-Hant (zh_TW.UTF-8 too)
shows zh-TW; zh-CN, zh-SG and zh-Hans-HK keep Simplified. The tray menu
and quota alerts follow the same choice. Release notes fall back to
Simplified.

The zh-TW strings are generated from the zh strings with opencc
(cn→twp), then corrected to Taiwan wording: 設定, 檔案, 帳號, 伺服器,
預設, 閘道, 連接埠, 參數, 選單, 透過, 取得, 用戶端, 存取, 終端機, 「」
quotes. A quota window is 時段 and a context window stays 上下文視窗.
Number units use 萬/億, and #unitsRow is shown for zh-TW.

Verified:
- New gui-zh-tw.test.cjs, in Chromium and WebKit:
  - key parity, placeholders and tags against zh;
  - no Simplified characters;
  - the Taiwan words;
  - locale detection for each system tag;
  - <html lang>, nav labels, fmtN units, the picker;
  - Settings Language at 1000px and 440px.
  With the sysLocale zh-TW line removed, it fails. On the base assets,
  all 3 fail.
- gui-ja, gui-de and i18n-keys pass.
- go vet, and go test -tags nogui for ./internal/gui and
  ./internal/settings, pass. That includes the new zh-TW cases in
  TestTrayLang / TestTrayLabels / quota_alert_test, which fail without
  the zh-TW branches.
2026-10-08 05:00:55 +08:00
forever 89b3a20f5d gui tests: a test run leaves no gui.test folder in the real ~/Library/WebKit or ~/Library/Caches (waitingsnow) (#1047)
TestTrayCellClickReleasedPanel shows its panel in the test binary run
again as a child. The package's TestMain gives the tests testenv's HOME,
but WebKit takes the home from the account's record, so every run wrote
the child's WebKit data to ~/Library/WebKit/gui.test and its caches to
~/Library/Caches/gui.test in the real home.

The child now starts through runAppKit (appkit_darwin_test.go). It sets
CFFIXED_USER_HOME, which CoreFoundation reads before the account's
record, to a temporary folder, so WebKit's data and caches go there. It
runs the binary under a name of its own, through a hard link: WebKit
names its folders after the process, whose name comes from the file, not
argv[0]. The test fails when a folder of that name is left in the real
~/Library/WebKit or ~/Library/Caches. The empty folders WebKit makes
under the name in the account's temporary folder (NSTemporaryDirectory,
which TMPDIR doesn't move) are removed. Wails v3 beta.24 offers no
non-persistent WKWebsiteDataStore: its windowNew makes a plain
WKWebViewConfiguration, which uses WebKit's default store.

CFFIXED_USER_HOME doesn't move the account's settings: cfprefsd still
serves the global domain and com.apple.universalaccess, where dark mode
and Increase Contrast are kept. A probe window read the same appearance,
Increase Contrast, Reduce Transparency, layout direction, languages and
non-default values (AppleShowScrollBars, AppleReduceDesktopTinting, the
zoom factor) with and without it, on macOS 26.7.1. Nothing is written
to the temporary home after the child exits.

Without CFFIXED_USER_HOME the test fails:

    trayusage_panel_darwin_test.go:20: the windows' process left /Users/wei/Library/WebKit/gui.test-1791307161567260000 in the account's real home
    trayusage_panel_darwin_test.go:20: the windows' process left /Users/wei/Library/Caches/gui.test-1791307161567260000 in the account's real home
    --- FAIL: TestTrayCellClickReleasedPanel (0.45s)

With it, it passes, and nothing new appears in the real ~/Library or in
the temporary folder.

PR #1042's TestMainWindowCorners and TestMainWindowRightToLeft, moved
onto runAppKit at its head f89991b1, pass and print the same with and
without CFFIXED_USER_HOME: corners 16.00pt (plain 16.00, toolbar
26.00), lights [[26 26] [49 26] [72 26]], title bar 66pt, a sheet 66pt
below the top, right to left lights [[874 26] [851 26] [828 26]].
Without it, both fail the same way.

go vet ./..., go build ./..., GOOS=linux go build -tags nogui ./...,
GOOS=windows go build ./..., go test -tags nogui ./..., go test
-count=1 ./internal/gui/... and gofmt -l pass under a temp HOME with
Go's caches pinned. docs/subsystems/gui-shell.md says it. Tests only.
2026-10-07 02:23:19 +08:00
tsuixl 29f4909262 fonts: list upright styles before slanted faces at the same weight (#1030)
Follow up on the Menlo style ordering noted in PR #1007. Compare normal, italic and oblique explicitly after weight, retaining the existing width and name tie-breakers.

The Menlo regression fails before the change with Italic, Regular, Bold Italic, Bold, and passes with Regular, Italic, Bold, Bold Italic. Group/tie-break tests, fresh Windows native discovery, focused nogui GUI/font tests, go vet and Windows/Linux nogui builds pass.
2026-10-06 21:15:19 +08:00
tsuixl bd33e575f1 gui: let users choose installed interface and code fonts (#1007)
* 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.
2026-10-06 19:32:56 +08:00
yetone 490d2ea362 gui: the tray panel's Allowances tab hides subscriptions and moves them, from an Arrange at its foot (H20 on Discord: "菜单栏的详情页能不能选择某些不显示,有点额度并不想看到或者可以调整顺序,目前的还不行") — the panel's tab already followed the Usage page's order (usageOrder, byUsageOrder), but it could be set only by dragging a card's logo on the window's Usage page, which nothing in the panel said, and there was no way to leave a subscription out of the panel at all. The tab's foot now has Arrange, and "{n} hidden" beside it when some are: Arrange lists a row a subscription (balances too) in the shared order, its logo the handle (a drag, or Alt+arrows; the grip by it on hover only, as on the Usage page) and a Hide/Show pill, under a note that hiding is the panel's only and the order is the Usage page's too; Done draws the cards again. What's hidden is kept in magpie's settings (settings.PanelUsageHidden, panelUsageHidden), not the browser: routing, caps, the Usage page and the menu bar's cells still have it, and a menu bar cell for a hidden subscription still opens its card (panelPeek) until the panel is put away or the tab left. POST /api/usage/arrange takes order and panelHidden and changes only the one it is sent (both were whole-order before; an order alone still works), and the Settings save keeps panelUsageHidden as it does usageOrder. Saves draw at once and are put back with the error if they fail; no click moves the page (the view's scroll is kept across the redraw); no native select, no left-border stripe. Which allowances the menu bar shows beside its icon stays Settings' pick (trayUsages). New t() strings have zh, ja and de. TestUsageArrangePanelHidden (each field set and cleared on its own, a Settings save between) passes -race -count=20, TestSettingsSaveKeepsWhatItDoesNotSend's fixture names the field. panel-arrange.test.cjs (440px: the hidden card left out and counted, Arrange keeping the reader's scroll, the rows and note, the hover-only grip, no stripe, no sideways overflow, Show/Hide posting panelHidden without a scroll and a failed save put back, Alt+arrow and a drag posting the order, Done's cards in it, a menu bar cell opening a hidden card until the panel hides, the Usage page keeping the hidden card) passes on Chromium and WebKit in en, zh, ja and de; click-scroll, gui-ja, gui-de, i18n-keys, panel-fold, panel-usage, quota-*, routing-quota-left, tray-cell-click, tray-inuse, tray-logos, tray-usages, usage-accounts(-fold), usage-arrange, usage-fold, usage-quota-head, usage-card-refresh, usage-stale-answer, usage-remote-cards, api-key-usage, claude-usage-asked and usage-checkin pass on both. go test -tags nogui ./internal/... under a temp HOME, go vet, gofmt -l, linux (nogui) and windows builds pass. Not tried in the real tray panel on a Mac. docs/reference.md and docs/subsystems/gui-shell.md say it. 2026-10-06 19:18:14 +08:00
forever f109b270c0 gui: the main window opens as it was last left — maximised again if it was, at its last size otherwise (#995)
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.
2026-10-06 16:09:58 +08:00
tsuixl db55bc8b7a gui: reject unknown Codex auto-review models from known providers (#938) (#943)
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.
2026-10-06 09:26:04 +08:00
2cc663d0a2 gui: protect unsaved edits and confirm destructive actions (#820)
* gui: edited forms ask before they are discarded, removals ask before they take effect, and the header keeps its first layout — Cancel, Escape, the backdrop and a page change could throw away a provider's draft, and several account, key, plugin and library removals took effect at once. Modified provider, settings, library and routing forms now ask before discarding; unmodified forms still close without asking, and refresh or closing the browser tab uses its leave confirmation. Removal and discard confirmations are in-app dialogs over the intact editor, centered, with Cancel focused first, Tab kept inside, Escape canceling and focus returned afterwards; destructive buttons have readable foregrounds in light and dark themes. Text and connection settings have Save/Cancel rather than saving on blur; simple switches still save at once. Editors associate labels and help with fields, make the background inert while open and keep focus through redraws; navigation keeps browser back/forward history and its current-page state, errors remain until dismissed or replaced, and focus and validation can scroll into view. Mobile settings tabs have larger targets, fields use 16px text and page zoom stays available. layout.js applies mode and platform before the header paints, theme.js applies the saved text size before styles, and mode-specific window controls stay hidden while app.js downloads. zh/ja/de strings added. routing-shape's global .rt-day selector selected the hidden By session button on WebKit; its history click now stays in .rt-days. ux-safety covers unchanged and modified closes, canceled and confirmed removals, Escape, backdrop, modal focus, light/dark centering and button contrast, explicit saves, browser history and sync-form discard. Rebased onto upstream/main 2f183c4c. Chromium and WebKit each pass 31 related browser tests in en and zh; gui-ja and gui-de pass all 6 browser and translation-parity tests. go vet -tags nogui ./..., go build -tags nogui, go test -tags nogui ./internal/gui, JavaScript syntax checks and git diff --check pass.

Co-authored-by: XYenon <register@xyenon.bid>

* gui: edited providers, routing groups and library forms ask before their drafts are discarded, and only immediate removals ask before taking effect — Settings keep saving on Enter and blur, switches save at once, and the reader's scroll position is restored after an unasked-for scroll. Header rows, fallback models, group members, patterns and rules stay ordinary reversible draft edits. The confirmation dialog keeps its own readable destructive colors; the window's text selection, muted and faint colors, timed status messages, replaceState navigation and iOS viewport policy stay as before. The keyboard scroll assertion is restored, and click-scroll's date buttons are scoped to .rt-days .rt-day so WebKit cannot pick a hidden request-grouping button. The safety tests cover dirty editor closes and page changes, immediate provider deletion, labels, modal focus, Escape and backdrop cancellation, with the withdrawn settings, history and persistent-error assertions removed. First-paint layout and the saved theme/text size remain early.

Co-authored-by: XYenon <register@xyenon.bid>

* gui: old WebKit gets its layout before the header paints, and browser tests answer the new discard and removal questions — compat.js runs before layout.js, whose viewport setup uses ES5; the confirmation uses vh rather than Safari 15.4 dynamic viewport units. Opening a provider from its address keeps the editor pending until its page loads, and fields keep their own nested labels when Names & levels is reopened. An empty model preference map is compared as Save sends it, so opening model settings without changing anything closes without a question. Removal tests assert nothing is sent before confirmation; draft tests answer Discard and retain their Save and Cancel assertions. Plugin migration uses the reader’s wheel rather than a scripted scroll, keeping every scroll assertion. Targeted WebKit regressions pass 40/40, Chromium regressions pass 41/41, and the model-setting and editor-safety checks pass in both engines. go vet -tags nogui ./..., go build -tags nogui, JavaScript syntax checks and git diff --check pass.

Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847
Co-authored-by: XYenon <register@xyenon.bid>

* gui tests: the upstream model list keeps folding with the new discard question — models-fold answers Discard only after picking another model; the short list and an unchanged folded list still close without asking. ux-safety waits for its Relay row rather than matching loading placeholders. Rebased onto upstream/main c353f7bd, including the new model-list expansion. models-fold passes in WebKit and Chromium, as do the English and Chinese editor-safety checks; the related model drafts and first-paint phone tests pass on WebKit.

Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847
Co-authored-by: XYenon <register@xyenon.bid>

* gui: an editor redraw keeps its removal confirmation and menus interactive — Only a fresh modal makes the existing background inert, and closing restores exactly the elements it changed. A redraw no longer disables a confirmation or popup appended over the editor; elements already inert and overlays added later retain their own state. The original background is retained if a new editor interrupts the closing animation. ux-safety redraws Relay twice under its Remove confirmation, clicks Cancel, and verifies no deletion was sent; it also picks a menu item after an editor redraw and checks background restoration. The regression reproduced on WebKit before the fix. All 8 English and Chinese safety checks pass on WebKit and Chromium. The related WebKit removal, draft, routing and phone checks pass, with account-arrange passing separately after a detached-element race in the concurrent run. go vet -tags nogui ./..., go test -tags nogui ./internal/gui, go build -tags nogui, JavaScript syntax and git diff --check pass.

Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847
Co-authored-by: XYenon <register@xyenon.bid>

* gui tests: editor safety uses the page's boot preferences, not its settings — gateway in boot.js is now the gateway-mode flag, whereas settings.gateway is the gateway's URL. Spreading settings into the fixture boot data mistook the URL for gateway mode and redirected a dirty editor's attempted page change back to Providers. The fixture now emits the same language, theme, text size and web fields as the server. The 8 English and Chinese editor-safety tests pass on Chromium and WebKit; gateway-mode, plugin middleware and plugin-market pass on WebKit, and phone-web passes separately after a scroll race under concurrent load. Rebased onto upstream/main 2f135ed4 with gateway-mode navigation and initialization retained. go vet -tags nogui ./..., go test -tags nogui ./internal/gui, JavaScript syntax and git diff --check pass.

Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847
Co-authored-by: XYenon <register@xyenon.bid>

* gui: upstream's new removals and fields follow the editor-safety rules too — Audited GUI changes from the original PR base 2f183c4c through upstream/main 9debc583, including earlier rebases. Bulk group removal uses the in-app consequence dialog rather than a second click, and clearing middleware options asks before deleting saved options. Cancel sends nothing and keeps the selection or editor. Custom group context, Settings' compact threshold, the inline key-weight input and middleware JSON have accessible names; the JSON note/error and group context hint are linked, and invalid input is exposed. The copy-out-of-date skill action is a keyboard button supporting Enter and Space. A skill's immediately saved per-agent transfer choices update the modal baseline, so Done, Escape and navigation no longer ask to discard already saved changes. Settings save behavior, draft-only removals, scroll guard, viewport and themes stay intact. Chinese, Japanese and German consequence strings added; obsolete bulk-removal armed styles and strings removed. The six affected browser files pass across Chromium and WebKit in English and Chinese (54 tests after scoping compact-at to its provider rather than skeletons). Additional provider price, key import/weight, group member/first-token, caller-key, purpose and gateway-mode regressions pass on WebKit; key-weight also passes in Chromium, with a concurrent status-message race passing separately. go vet -tags nogui ./..., go test -tags nogui ./internal/gui, go build -tags nogui, JavaScript syntax and git diff --check pass. The Chinese bulk confirmation was rendered and inspected on WebKit.

Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847
Co-authored-by: XYenon <register@xyenon.bid>

* gui: new upstream key removals and nested group jumps keep the editor-safety rules — Bulk deletion of failing, off and matching keys uses the app's consequence dialog, with Cancel and Escape sending nothing and a redraw leaving its buttons interactive. The shared group-editor entry asks before replacing a dirty draft, and nested navigation waits for acceptance before upstream's explicit scroll. Codex's Sign out uses its existing confirmation once. Group and key filters have accessible names. A focused model-price input is included in leave protection before its change/blur parser runs, and reverting the raw value is clean. Settings saving, scroll protection, draft-only removal, viewport and themes retain their upstream behavior. The affected flows pass in Chromium and WebKit, English and Chinese; Names & levels also passes in Japanese and German. The price fixture focuses before filling because the previous blur normalizes sibling fields, a race reproduced on upstream. go vet -tags nogui ./..., go test -tags nogui ./internal/gui, go build -tags nogui, JavaScript syntax and git diff --check pass. The Chinese bulk-key dialog was rendered and inspected on WebKit.

Co-authored-by: XYenon <register@xyenon.bid>
Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847

* gui tests: Codex's newly removable active account waits for the consequence question (#820) — The latest upstream multiaccount Remove path uses accountAction's immediate-removal dialog. Its test now verifies that the first click sends nothing, Cancel keeps the account, and accepting sends exactly one login/forget request. The original account, status and translation assertions remain. Rebased onto upstream/main 8d3cf5f8; the final Codex, nested-group, bulk-key, focused-price and editor-safety tests pass 48/48 across Chromium and WebKit in English and Chinese. Upstream's new quota, skill-group, hit-rate and translation checks pass; search-first's detached-element failure reproduces on isolated upstream. The complete WebKit suite ran 1413 tests before this final upstream delta, with 1289 passes and 124 failures; failing files were compared with upstream or rerun separately. Syntax and diff checks, go vet and the nogui build pass.

Co-authored-by: XYenon <register@xyenon.bid>
Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847

* gui: the Sessions agent strip reports which reused button is selected (#820, #929) — Rebased onto upstream/main 1330b966, retaining the strip's own scrolling, visible selected agent and preserved buttons. Each redraw updates aria-pressed for the selected agent and clears it for the previous selection. The regression checks All initially, the unselected alternatives, then Zed selected with All cleared; on unmodified upstream it fails with null instead of true. Both strip files pass 8/8 across Chromium and WebKit in English and Chinese, including narrow and wide layout and scroll assertions. The latest ChatGPT API, Codex and editor-safety browser checks pass 32/32. Final GUI, Agent, Provider and Gateway Go checks, go vet -tags nogui ./..., Linux nogui and Windows builds, syntax and diff checks pass. Linux native GUI build is blocked by the orb's GTK 4.8 lacking APIs used by Wails beta.24. The complete WebKit run finished with 1289 passes and 124 failures; all failing files were checked against upstream or rerun separately, with details retained in the review artifacts. The full Go suite's sole sessions rewrite failure also reproduces on upstream.

Co-authored-by: XYenon <register@xyenon.bid>
Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847

* gui: closing editors immediately give clicks back to the page (#820, #944) — Restore the background when the close animation starts and protect it again when an editor reopens. Both New group buttons and the model entry ask before replacing a dirty routing draft; creating a group from a provider waits for navigation to be accepted. The GUI shell reference describes these transitions.

The real mouse click during closing fails without the change in all four engine/language combinations, with null instead of received. The unguarded New group and model entries fail waiting for the discard question; the old model navigation creates a routing draft even after Cancel. ux-safety, provider-remove, click-scroll and group-new-head pass 50/50 in Chromium and WebKit, English and Chinese. Closing/reopening also passes five additional rounds, 20/20. After the final upstream translation and TUI additions, i18n-keys, Japanese/German, ux-safety and group-new-head pass 23/23. Ten Chromium update-wait runs have no no-click-scroll failure; two fail on the Restarting status assertion, also reproduced on upstream.

The complete WebKit run finished with 1,307 passes and 141 failures across 1,448 tests, with no skips or cancellations. It is not green: failures in 63 of its 70 failing files also occur on upstream; the other seven files pass separate PR reruns. The complete suite preceded the final upstream translation deduplication, whose resolved keys and values are unchanged, and the TUI/docs additions. go vet and the Linux GUI build with the Makefile's gtk3 tag, Linux nogui and Windows builds, GUI/TUI Go tests, syntax and diff checks pass. Full nogui Go tests are not green: the sessions same-size rewrite check and the final run's WebDAV slow-transfer stall both reproduce on upstream with -race -count=20 under an isolated HOME with pinned caches.

Co-authored-by: XYenon <register@xyenon.bid>
Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-10-06 06:57:49 +08:00
yetone a5df344e43 docs: code-standards gets the acceptance criteria a change must show before merge or release and the PR review patterns, and docs/subsystems covers gateway routing, agent wiring, providers and accounts, the library and the GUI shell (Junjie Zhou on Discord)
code-standards.md: a failing test before the change (a real FAIL, not a compile error), tests under a temp HOME with GOPATH/GOMODCACHE/GOCACHE pinned and never a live agent config, vet/build (linux nogui, windows)/test with pipefail, GUI tests in Chromium and WebKit with gui-ja/gui-de, real-account checks in a sandbox, the UI rules; a PR merges only after it is run merged onto main and with --match-head-commit; a release counts only with the tag on main, 16 assets and Latest. The review section is distilled from recent PR reviews (#706–#931).

New references, each following the README's format and linking real source: gateway-routing.md (plan/planGroup, rests, sink, affinity, attempt/translate), agent-wiring.md (Agent fields, ConnectHow, applied.json drift, stash, DisconnectPreview, Reseat/Rewire, WSL twins and ListsFor), providers-accounts.md (providers.json, keys, Accounts, token refresh, logins, groups, Quotas), library.md (change/sync, instructions, MCP formats, skills link/copy, backups, RTK), gui-shell.md (Run, startBackend, Handler/held/revalidated, magpie web, gateway mode, nogui). Every link and backticked identifier was checked against the tree at origin/main. Docs only.
2026-10-06 02:17:14 +08:00