settings, gui: Windows and Linux start at 110% text size until one is picked, and a new text size leaves no blank room at the page's foot (#914, #911, emo172)

This commit is contained in:
yetone committed 2026-10-05 22:50:37 +08:00
1 parent b358a5ca83
commit 4ff1d51690
5 files changed
+110 -6

No files matched your search

+10 -1
View File
@@ -15903,9 +15903,18 @@ function applyZoom(n) {
document.documentElement.style.setProperty("--zoom", web ? 1 : (n || 100) / 100);
}
let textSize = window.bootPrefs?.textSize || 100;
// A new text size lays every page out anew: what was clicked (the size's
// own button) isn't held where it was, nor any room kept for it, or the
// page zoomed out at its foot was given a blank as tall as it shrank
// (#911). Nothing is scrolled; the browser fits the page as it is.
function newTextSize() {
held = null;
for (const v of document.querySelectorAll(".view")) setRoom(v, 0);
}
async function setTextSize(n) {
if (web || n === textSize) return;
textSize = n;
newTextSize();
applyZoom(n);
try {
prefs = await writingPrefs(api("settings/text-size", { size: n }));
@@ -15987,7 +15996,7 @@ function applyPrefs(s, rate) {
if (applyPrefs.painted) { renderCosts(); if (mode === "panel" && panelTab === "stats") renderPanelUse(); }
}
applyPrefs.painted = true;
if (!prefsBusy && (s.textSize || 100) !== textSize) { textSize = s.textSize || 100; applyZoom(textSize); }
if (!prefsBusy && (s.textSize || 100) !== textSize) { textSize = s.textSize || 100; newTextSize(); applyZoom(textSize); }
const was = locale;
setLocale(s.lang);
if (was !== locale && mode === "window") queueMicrotask(() => slide($("#nav"), "nav"));
+5 -2
View File
@@ -566,8 +566,11 @@ leaves it be; Ctrl/Cmd + = and − step through the sizes and 0 goes back to
The zoom is the webview's, so here it is as a browser zooms: the smallest
window at 150% (840×630 points) is 560×420 CSS pixels at a device scale of
1.5, and no view (nor the panel at 150%) runs off to the side, the Mac
header still 50 points tall for the traffic lights. English and Chinese,
light and dark; ARTIFACT_DIR gets screenshots at 100% and 150%.
header still 50 points tall for the traffic lights. At 150% in a 1440×900
window scrolled down, a click on 100% (the window then 1440×900 CSS pixels)
leaves no `.view-room` and no blank under the page, nor scrolls it beyond
the browser's own fit (#911). English and Chinese, light and dark;
ARTIFACT_DIR gets screenshots at 100% and 150%.
`settings-groups.test.cjs` puts the Settings page's warm-ups and check-in
under a tab per service (#124): Codex, Claude Code and WorkBuddy tabs where a
+44
View File
@@ -201,6 +201,50 @@ for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium",
await page.context().close();
});
await t.test(lang + ": 100% picked at the foot of the page leaves no blank under it", async () => {
// #911: at 150% scrolled to the foot, a click on 100% held the
// control where it was as the webview zoomed out, and the page grew
// a blank foot to hold it; Ctrl+0 went to the top. The page laid out
// anew is the browser's to fit: no room, nothing scrolled.
const posts = [];
// emo172's window: 1440×900 points, 960×600 CSS pixels at 150%
const page = await open(lang, "light", 150, posts, { width: 960, height: 600, zoom: 1.5 });
await page.locator("#prefs").click();
const segs = page.locator("#textSizeSegs .opt");
await segs.first().waitFor();
const v = page.locator("#view-settings");
await v.hover();
// down as far as the control is still in sight: to the foot, or the
// control at the view's top
for (let i = 0; i < 400; i++) {
if (await v.evaluate((v) => v.scrollTop >= v.scrollHeight - v.clientHeight - 1
|| document.querySelector("#textSizeSegs").getBoundingClientRect().top < v.getBoundingClientRect().top + 60)) break;
await page.mouse.wheel(0, 40);
await page.waitForTimeout(20);
}
await page.waitForTimeout(300);
const before = await v.evaluate((v) => v.scrollTop);
assert(before > 0, "the settings page must scroll");
await segs.nth(0).click();
await page.locator("#textSizeSegs .opt.on", { hasText: "100%" }).waitFor();
// the webview's zoom out: the same window is half as many CSS pixels again
await page.setViewportSize({ width: 1440, height: 900 });
await page.waitForTimeout(600);
const foot = await v.evaluate((v) => {
const kids = [...v.children].filter((c) => !c.classList.contains("view-room") && c.getClientRects().length);
const end = Math.max(...kids.map((c) => c.getBoundingClientRect().bottom));
return { room: !!v.querySelector(":scope > .view-room"), blank: v.getBoundingClientRect().bottom - end, top: v.scrollTop, max: v.scrollHeight - v.clientHeight };
});
assert.deepEqual(posts, [100]);
assert.equal(foot.room, false, "no room may be left at the foot");
// a page that still scrolls reaches the window's foot (one shorter has its own)
assert(foot.max < 1 || foot.blank < 60, `a blank of ${foot.blank}px under the page`);
// nothing scrolled: where it was, or as far down as the page now goes
assert(Math.abs(foot.top - Math.min(before, foot.max)) < 2, `scrolled from ${before} to ${foot.top} (max ${foot.max})`);
assert.deepEqual(page.errors, []);
await page.context().close();
});
for (const theme of ["light", "dark"]) {
await t.test(lang + ", " + theme + ": nothing runs off the side at 150%", async () => {
// the smallest window at 150% is 840×630 points, 560×420 CSS pixels
+14 -1
View File
@@ -17,6 +17,7 @@ import (
"path/filepath"
"reflect"
"regexp"
"runtime"
"slices"
"strings"
"sync"
@@ -517,6 +518,18 @@ var (
TextSizes = []int{100, 110, 125, 150}
)
// DefaultTextSize is the text size of settings that have never had one
// saved. Windows' and Linux's body text is 14px against magpie's 13px at
// 100%, a size smaller than what is around it there, so they start at
// 110% (#914); the Mac's is 13px, as magpie's. A size once saved, 100%
// too, is kept as it is. A var so tests can be any system.
var DefaultTextSize = func() int {
if runtime.GOOS == "windows" || runtime.GOOS == "linux" {
return 110
}
return 100
}
var validTerminalBundleID = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9.-]{0,254}$`)
// providerID is how a provider's id is spelled: lower-case letters, digits
@@ -911,7 +924,7 @@ func (s Settings) normal() Settings {
s.UpdateEvery = 360
}
if s.TextSize == 0 {
s.TextSize = 100
s.TextSize = DefaultTextSize()
}
// one card, as a magpie before TrayUsages kept it; an empty list
// sent on purpose (all of them turned off) stays empty
+37 -2
View File
@@ -11,6 +11,7 @@ import (
"strings"
"testing"
"github.com/yetone/magpie/internal/filememo"
"github.com/yetone/magpie/internal/redact"
)
@@ -56,8 +57,8 @@ func TestRoundTrip(t *testing.T) {
if Save(Settings{SessionTerminal: "Ghostty; rm -rf /"}) == nil {
t.Fatal("bad terminal app id accepted")
}
// the text size is 100% until one of the sizes is chosen
if Save(Settings{}) != nil || Load().TextSize != 100 {
// the text size is the system's default until one of the sizes is chosen
if Save(Settings{}) != nil || Load().TextSize != DefaultTextSize() {
t.Fatalf("default text size: %+v", Load())
}
if Save(Settings{TextSize: 125}) != nil || Load().TextSize != 125 {
@@ -500,3 +501,37 @@ func TestAlertsChecked(t *testing.T) {
t.Fatalf("read back %d %v", s.UsageAlert, s.BalanceAlert)
}
}
// Windows and Linux start at 110% (#914), the Mac at 100%; a size saved
// before, 100% too, is kept on any of them.
func TestDefaultTextSize(t *testing.T) {
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
orig := DefaultTextSize
t.Cleanup(func() { DefaultTextSize = orig })
for _, c := range []struct {
goos string
want int
}{{"windows", 110}, {"linux", 110}, {"darwin", 100}} {
DefaultTextSize = func() int { return c.want }
if err := os.Remove(Path()); err != nil && !os.IsNotExist(err) {
t.Fatal(err)
}
filememo.Forget()
if got := Load().TextSize; got != c.want {
t.Fatalf("%s, nothing saved: text size %d, want %d", c.goos, got, c.want)
}
if err := Save(Settings{TextSize: 100}); err != nil {
t.Fatal(err)
}
if got := Load().TextSize; got != 100 {
t.Fatalf("%s: a saved 100%% became %d", c.goos, got)
}
}
want := 100
if runtime.GOOS == "windows" || runtime.GOOS == "linux" {
want = 110
}
if orig() != want {
t.Fatalf("on %s the default is %d, want %d", runtime.GOOS, orig(), want)
}
}