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:
1 parent
b358a5ca83
commit
4ff1d51690
5 files changed
+110
-6
No files matched your search
@@ -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"));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user