Files
aside: magpie picks Aside's model, its thinking level, its four task models and its picture model (#789)
gui: a gateway key's limit window and the session terminal are picked from the app's own menu too, so no native select is left (the owner, on the account picker: 这里为啥是原生的 selector 而不是我们抽象的 ComboBox?) — after the keep-signed-in account picker, two <select>s remained: the Window of a gateway key's limit editor (#585) and Settings → Session terminal. Each is now a sess-pick button showing what is picked, with a chevron, opening the app's menu (openProtoMenu) headed Limit window / Session terminal (限额周期 / 会话终端) with the current one ticked. The limit window's pick is staged in the editor as before (nothing sent until Save) and fills its column with the chevron at its end; the terminal's is drawn at once and saved as before. gateway-key-limit.test.cjs and session-terminal.test.cjs now check there is no native select, the menu's head and items, the button's value and text, a pick closing the menu (and leaving the limit editor open), a second click closing it, and picking through the menu, posting as before; they, keep-login, click-scroll and settings-sections pass in WebKit, en and zh.
gui: the page runs in macOS 12's WebKit — no regex lookbehind, the few newer built-ins filled in, fallbacks for container queries, subgrid, :has and color-mix (#220 uclort: on macOS 12.7.6 the app opened blank, the panel showing its tab bar, a "Profiles + Save current" heading and "Open magpie · Quit" and nothing else, no button working) — the page runs in the system's WebKit, on macOS 12 Safari 15.x's, and app.js's snippet highlighter had a regex lookbehind, (?<==)\S+ (app.js:2167), which WebKit only parses from Safari 16.4: a SyntaxError in a literal fails the whole file, so app.js never ran (and routing.js and library.js, which lean on it, threw), leaving index.html's static markup; library.js had two more, (?<![\w-]) in the SVG greying (library.js:162-163). The highlighter now finds a value after "=" by hand (the character before the match, a sticky \S+) and the greying checks the character before a colour name in its replace callback, each giving the same output as before (compared on shell, curl, Python and Node snippets and an SVG with attribute and style colours, data-fill and x-color left alone). Built-ins newer than Safari 15.0 in use, routing.js:307's findLast and library.js:1042's structuredClone (15.4), are filled in by a new compat.js loaded before the other scripts (with at, findLastIndex and Object.hasOwn), each only where missing; structuredClone there is a JSON copy, enough for the API's plain data. Styles: a selector list with :focus-visible (15.4) drops the whole rule before 15.4, so the agent handle's hover, an account's Remove and protocol buttons showing on hover, the protocol menu's hover and the editor's focus ring are split from their :focus-visible (or it is dropped where :focus covers it); .acc:has(.aq) and the panel's .profiles:has(> .chip-input) hint (15.4) are classes set by app.js (with-aq, naming); the Routing page's container queries and the request list's subgrid (16) get @supports not fallbacks by the window's width (the tray panel is narrower than all of them) and a row's own columns; and the panel's --panel-tint, a color-mix() (16.2) in a custom property that would have gone invalid and painted the panel clear (tintPanel then handing the system white under dark text), falls back to --bg, as the floating status and Library's sticky head do. Other things newer than 15.0 are left as cosmetic: inert (15.5) on the folded agent list, accent-color, the rest of color-mix() on hover and focus shades, .quota's subgrid rows. The build: Go 1.25 and later run on macOS 12 and up only (the linker stamps 12.0 as the oldest), so Info.plist's LSMinimumSystemVersion and the Makefile's -mmacosx-version-min go from 11.0 to 12.0 and the site says macOS 12+ rather than 11+. Tested with a new internal/gui/tests/old-webkit.test.cjs: every assets/*.js parsed with the Babel parser Playwright bundles, failing on a regex lookbehind, v flag or modifiers (in a literal or new RegExp("…")), a class static block, decorators, import attributes or using (checked against the old highlighter's regex and a static block, and not on named groups, lookahead, ??= or private methods); a grep for built-ins newer than 15.0 (at, findLast, structuredClone, hasOwn, toSorted and co., Set methods, groupBy, withResolvers, Iterator helpers, popover …) unless compat.js fills them in (fails on main's routing.js:307 and library.js:1042 without it); compat.js the first of the page's scripts and every script loaded; the CSS with no :has(), no :focus-visible in a mixed list, no nesting or dvh, and @container, subgrid columns and a color-mix() custom property only with an @supports not fallback in the same file; and, in WebKit with those built-ins deleted, the panel's tabs, Save current's name field and every window page drawn with no page error. Documented in internal/gui/tests/README.md. go build -tags nogui (and GOOS=linux, GOOS=windows), go build (darwin GUI), go test -tags nogui ./... and the whole GUI suite (130 tests) pass. Not tried on a real macOS 12 or Safari 15: the old engine's parse is judged by the parser, not run.
library: skills are picked to turn on or off together, and a part of a list (lark-*) has its own chips (#791, mintonight: 资源库中local中的技能,能不能支持多选分组,或者支持多选来开启或关闭 … lark-* 我希望把他们归到一起统一来管理) — the skills on this computer are one list with no heading when they're all from there, so #787's group chips never showed for them, and Feishu's lark-* skills were turned on or off one row at a time. A part of a big list (its skills' shared name start, or their folder) now has chips on its heading, as a repository's heading does: lit when an agent has them all, half lit when it has some, a click gives it every one or takes them all, All does it for every agent; a click doesn't fold the part. Select, beside Turn all on, puts a box on every skill's row and on every heading (ticked when all under it are picked, half with some), a row's click picks it instead of opening its SKILL.md, and a bar that stays in view under the list says how many are picked, has a box for every skill the filter shows ("All 4 that match"), the same chips for the picked ones, Clear and Done; the picks stay through a change and Done takes the boxes away. A pick doesn't scroll the page. zh and ja strings added. pick-skills.test.cjs (WebKit, en and zh): the lark part's Codex chip posts the four lark skills, rows and headings pick, the bar's chips post the picked ones, the filter's All picks what it shows, Done ends it; it fails on the old code. Looked at in light and dark. group-skill-chips, every-skill, library-all, library-items, library-toggle-status, library-row-open-scroll, own-skill, remove-all-skills, shared-skills, import-all-skills, library-own-folder and library-loading pass on WebKit; Chromium doesn't launch in this sandbox. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./internal/gui/ under a temp HOME pass.
library: skills are picked to turn on or off together, and a part of a list (lark-*) has its own chips (#791, mintonight: 资源库中local中的技能,能不能支持多选分组,或者支持多选来开启或关闭 … lark-* 我希望把他们归到一起统一来管理) — the skills on this computer are one list with no heading when they're all from there, so #787's group chips never showed for them, and Feishu's lark-* skills were turned on or off one row at a time. A part of a big list (its skills' shared name start, or their folder) now has chips on its heading, as a repository's heading does: lit when an agent has them all, half lit when it has some, a click gives it every one or takes them all, All does it for every agent; a click doesn't fold the part. Select, beside Turn all on, puts a box on every skill's row and on every heading (ticked when all under it are picked, half with some), a row's click picks it instead of opening its SKILL.md, and a bar that stays in view under the list says how many are picked, has a box for every skill the filter shows ("All 4 that match"), the same chips for the picked ones, Clear and Done; the picks stay through a change and Done takes the boxes away. A pick doesn't scroll the page. zh and ja strings added. pick-skills.test.cjs (WebKit, en and zh): the lark part's Codex chip posts the four lark skills, rows and headings pick, the bar's chips post the picked ones, the filter's All picks what it shows, Done ends it; it fails on the old code. Looked at in light and dark. group-skill-chips, every-skill, library-all, library-items, library-toggle-status, library-row-open-scroll, own-skill, remove-all-skills, shared-skills, import-all-skills, library-own-folder and library-loading pass on WebKit; Chromium doesn't launch in this sandbox. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./internal/gui/ under a temp HOME pass.
gui: one list of sessions — the Sessions page shows what each session spent, and Usage → Sessions' latest sessions open there (#752, hisiling on v0.1.814: the top bar's 会话 listed every session by agent and folder with resume, delete and the trash but no tokens or cost, while 用量 → 会话's 最近的会话 had folder, model, tokens and cost and resumed too but couldn't delete or group by folder; both opened to the same time, folder, session id and resume command, so the same list was drawn twice with half missing from each). /api/sessions/manage already carried each session's tokens, cost and models (sessions.Managed embeds Session, and ListAgent assembles it priced), the page just never drew them; its rows now end in the same two columns Usage's list has (sessSpent: tokens with in/out/cached under them, ≈cost, "—" with its reason where there is no known price), the sub line names the models and the gateway's route (sessModelsText, shared with Usage), the details gain the per-model breakdown and the Routed lines (sessUsageDetail, which sessionDetail now uses too, so the top-sessions card is as before), and its filter also matches a model. manage now also sends each session's via (usage.Vias since the agent's oldest session, as /api/sessions does), so routing isn't lost; a null models list is read as empty. Usage → Sessions keeps its statistics, charts, top sessions and its latest-sessions list, as that list follows the range, agent, model and folder filters the Sessions page doesn't have: its rows keep title, folder, models, tokens and cost, lose Resume, Open in session terminal and the in-place details, and open on the Sessions page instead (role=link, Enter/Space too, a chevron, title "Open in Sessions" / 在会话页打开): the session's agent is picked, its folder unfolded, the row opened, and once the list is drawn the row is brought into sight — a scroll the reader's click asked for (scrollOnPurpose(e) at the click, then readerScrolls and readerLeaves when the row arrives, as the quota cards' focus does). An All sessions / 全部会话 button by the list's search opens the page on the agent picked there. At 560px and below a Sessions page row drops its tokens column, keeping the cost, so the title keeps room beside Resume, terminal and delete. The other way round (deleting and the trash moved into Usage) was not taken: the Sessions page's per-agent, per-folder tree with picking and the trash is the page made for it, and Usage's list is a drill-down of its filters. No <select>, no left-border accent, no click scrolls on its own, the Agents grip untouched. The GUI has en and zh tables only; both have the new strings. TestSessionsManageCarriesUsage (a Claude session from testdata plus a gateway record: tokens, models and via in sessions/manage) fails on the old handler; sessions-usage-open.test.cjs (WebKit, en and zh) fails on the old assets and passes, and sessions-*, click-scroll, usage-fold and routing-sessions pass; Chromium doesn't launch on this machine. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with real session files beyond the fixtures.
gui: one list of sessions — the Sessions page shows what each session spent, and Usage → Sessions' latest sessions open there (#752, hisiling on v0.1.814: the top bar's 会话 listed every session by agent and folder with resume, delete and the trash but no tokens or cost, while 用量 → 会话's 最近的会话 had folder, model, tokens and cost and resumed too but couldn't delete or group by folder; both opened to the same time, folder, session id and resume command, so the same list was drawn twice with half missing from each). /api/sessions/manage already carried each session's tokens, cost and models (sessions.Managed embeds Session, and ListAgent assembles it priced), the page just never drew them; its rows now end in the same two columns Usage's list has (sessSpent: tokens with in/out/cached under them, ≈cost, "—" with its reason where there is no known price), the sub line names the models and the gateway's route (sessModelsText, shared with Usage), the details gain the per-model breakdown and the Routed lines (sessUsageDetail, which sessionDetail now uses too, so the top-sessions card is as before), and its filter also matches a model. manage now also sends each session's via (usage.Vias since the agent's oldest session, as /api/sessions does), so routing isn't lost; a null models list is read as empty. Usage → Sessions keeps its statistics, charts, top sessions and its latest-sessions list, as that list follows the range, agent, model and folder filters the Sessions page doesn't have: its rows keep title, folder, models, tokens and cost, lose Resume, Open in session terminal and the in-place details, and open on the Sessions page instead (role=link, Enter/Space too, a chevron, title "Open in Sessions" / 在会话页打开): the session's agent is picked, its folder unfolded, the row opened, and once the list is drawn the row is brought into sight — a scroll the reader's click asked for (scrollOnPurpose(e) at the click, then readerScrolls and readerLeaves when the row arrives, as the quota cards' focus does). An All sessions / 全部会话 button by the list's search opens the page on the agent picked there. At 560px and below a Sessions page row drops its tokens column, keeping the cost, so the title keeps room beside Resume, terminal and delete. The other way round (deleting and the trash moved into Usage) was not taken: the Sessions page's per-agent, per-folder tree with picking and the trash is the page made for it, and Usage's list is a drill-down of its filters. No <select>, no left-border accent, no click scrolls on its own, the Agents grip untouched. The GUI has en and zh tables only; both have the new strings. TestSessionsManageCarriesUsage (a Claude session from testdata plus a gateway record: tokens, models and via in sessions/manage) fails on the old handler; sessions-usage-open.test.cjs (WebKit, en and zh) fails on the old assets and passes, and sessions-*, click-scroll, usage-fold and routing-sessions pass; Chromium doesn't launch on this machine. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with real session files beyond the fixtures.
gui: the page is first drawn in its saved theme, not the system's (seg-contrast failed in Chromium on the release branch: in dark the view shown read at 3.96:1, the other views at 4.69:1) — the saved theme was put on the page by app.js, at the foot of index.html, so a window kept dark under a light system had its header parsed and styled light first, and its buttons' colour transitions (the switch's views, Sync, Settings) then faded them to dark as the page loaded; the test read them mid-way, the more so as #471 made the page longer to parse. theme.js, run in the head after boot.js and before the styles, sets it from bootPrefs (or ?theme=) for the first paint; app.js still sets it as before. seg-contrast.test.cjs now holds app.js back and checks the page was already drawn in its theme (fails without theme.js, Chromium and WebKit, en and zh, dark), and reads colours once the page's transitions are over. old-webkit lets theme.js (ES5, URLSearchParams) run before compat.js; api-key-theme, from the release branch, expects a text button in --ctl-fg as #477 set. seg-contrast, settings-sections, header-zoom-fit, header-centre, click-scroll, old-webkit, api-key-theme, omarchy, agent-grip, web-page, gateway-fold, sessions-toolbar pass; go vet, go build -tags nogui, go test ./internal/gui pass.