From 07cb38b755a6e1990bacfc30261127688b0564f8 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Fri, 2 Oct 2026 23:05:32 +0200 Subject: [PATCH 1/3] fix(ui): scroll chip instead of a scrollbar; solid teal tray toggles in Wind grey (#329) The Settings page hides its native scrollbar and shows one small fixed-size chip on the right edge, only when there is more to scroll; it follows the scroll and can be dragged. In Wind grey the tray ON toggle segments are solid teal with a near-black icon, the black-and-teal contrast of the Settings switches, instead of the dim teal tint. Version 0.20.2. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KPUNAWcwghXdHCApcKKjSG --- src/tray_app/flyout_palettes.h | 2 +- src/version.h | 4 +-- tests/test_flyout_palettes.cpp | 3 +- ui/src/Settings.svelte | 6 +++- ui/src/controls/Preview.svelte | 2 +- ui/src/shell/ScrollChip.svelte | 61 ++++++++++++++++++++++++++++++++ ui/tools/gen-flyout-palettes.cjs | 4 ++- 7 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 ui/src/shell/ScrollChip.svelte diff --git a/src/tray_app/flyout_palettes.h b/src/tray_app/flyout_palettes.h index d99f838..28a8b10 100644 --- a/src/tray_app/flyout_palettes.h +++ b/src/tray_app/flyout_palettes.h @@ -27,7 +27,7 @@ struct FlyoutPalette { inline constexpr int kPaletteCount = 4; inline const FlyoutPalette kPalettes[kPaletteCount] = { { "grey", 10, 8, 6, - { 0x000000, 0x121212, 0x191919, 0xf2f2f2, 0xd0d0d0, 0xb4b6ba, 0x18191a, 0x2d2d2d, 0xb0b0b0, 0x2fbfa5, 0x2fbfa5, 0x000000, 0x0b0b0b, 0x000000, 0x0a0a0a, 0x3d3d3d, 0x0e0e0e, 0x1c1c1c, 0xc8cad0, 0xa9ece0, 0x1f5650, 0x266560, 0x2fbfa5, 0x222222, 0xf2f2f2, 0x000000, 0x222222, + { 0x000000, 0x121212, 0x191919, 0xf2f2f2, 0xd0d0d0, 0xb4b6ba, 0x18191a, 0x2d2d2d, 0xb0b0b0, 0x2fbfa5, 0x2fbfa5, 0x000000, 0x0b0b0b, 0x000000, 0x0a0a0a, 0x3d3d3d, 0x0e0e0e, 0x1c1c1c, 0xc8cad0, 0x04201b, 0x2fbfa5, 0x45cbb3, 0x2fbfa5, 0x222222, 0xf2f2f2, 0x000000, 0x222222, 0.f, 0.55f, 0.4f, 0.62f, 0.f } }, { "ember", 10, 8, 6, { 0x0c0908, 0x130e0b, 0x261d17, 0xf5eee7, 0xd8cbbf, 0xa39382, 0x1f1611, 0x1d1612, 0xb9a898, 0xe48a45, 0xe48a45, 0x000000, 0x130e0b, 0x0c0908, 0x130e0b, 0x3a2e25, 0x18120e, 0x251f1b, 0xb9a898, 0xe9a876, 0x704c31, 0x7e5433, 0xe48a45, 0x3a2c23, 0xe48a45, 0xff8a3d, 0x3a2c23, diff --git a/src/version.h b/src/version.h index 0971ade..7b876e2 100644 --- a/src/version.h +++ b/src/version.h @@ -4,7 +4,7 @@ #define WIND_VER_MAJOR 0 #define WIND_VER_MINOR 20 -#define WIND_VER_PATCH 0 +#define WIND_VER_PATCH 2 // String form for logs/snapshot/UI. Keep in sync with the numeric parts above. -#define WIND_VERSION_STR "0.20.0" +#define WIND_VERSION_STR "0.20.2" diff --git a/tests/test_flyout_palettes.cpp b/tests/test_flyout_palettes.cpp index 736e45f..3165d2d 100644 --- a/tests/test_flyout_palettes.cpp +++ b/tests/test_flyout_palettes.cpp @@ -41,7 +41,8 @@ TEST_CASE("flyout palettes: Wind grey keeps the look the flyout already had") { const FlyoutPalette& g = kPalettes[0]; CHECK(g.dark.menu == 0x000000); CHECK(g.dark.fill == 0x2fbfa5); - CHECK(g.dark.on == 0x1f5650); + CHECK(g.dark.on == 0x2fbfa5); // solid teal ON segments with a near-black icon (#329) + CHECK(g.dark.onic == 0x04201b); CHECK(g.dark.tintA == 0.f); } diff --git a/ui/src/Settings.svelte b/ui/src/Settings.svelte index 3443703..bcd6e16 100644 --- a/ui/src/Settings.svelte +++ b/ui/src/Settings.svelte @@ -17,6 +17,7 @@ import { droppedBinds } from './lib/keybindRules.js'; import TitleBar from './shell/TitleBar.svelte'; import Sidebar from './shell/Sidebar.svelte'; + import ScrollChip from './shell/ScrollChip.svelte'; import Banner from './shell/Banner.svelte'; import Card from './shell/Card.svelte'; import SaveCapsule from './shell/SaveCapsule.svelte'; @@ -317,6 +318,7 @@ {/key}
+
{announcement}
@@ -404,7 +406,9 @@ .app.pending { visibility: hidden; } .app { width: 100vw; height: 100vh; display: grid; grid-template-rows: 38px 1fr; position: relative; overflow: hidden; } .body { display: grid; grid-template-columns: 240px 1fr; min-height: 0; } - .main { position: relative; min-height: 0; overflow-y: auto; padding: 0 40px; outline: none; } + .body { position: relative; } /* the scroll chip sits on the right edge of the page column */ + /* No native scrollbar on the page: ScrollChip shows the position instead (#329). */ + .main { position: relative; min-height: 0; overflow-y: auto; padding: 0 40px; outline: none; scrollbar-width: none; } .main[data-page="tray"] :global(.banner + .card) { margin-top: 20px; } /* k01: a caption-less first card sits 20px under the band */ .page.fade { animation: pagein var(--dur-fast) var(--ease); } @keyframes pagein { from { opacity: 0; } } diff --git a/ui/src/controls/Preview.svelte b/ui/src/controls/Preview.svelte index faccb37..aafb21d 100644 --- a/ui/src/controls/Preview.svelte +++ b/ui/src/controls/Preview.svelte @@ -23,7 +23,7 @@ const visible = (r) => !r.showIf || String(values[r.showIf.key]) === String(r.showIf.eq); const set = (key, val) => { values[key] = val; calls.push([key, val]); }; const extra = { - runningModel: 'hybrid', mpoNeedsRestart: false, version: '0.20.0', + runningModel: 'hybrid', mpoNeedsRestart: false, version: '0.20.2', onRestart: () => calls.push('restart'), onAction: (a) => calls.push(['action', a]), onRepo: () => calls.push('repo'), pick: async () => 'RDR2.exe', diff --git a/ui/src/shell/ScrollChip.svelte b/ui/src/shell/ScrollChip.svelte new file mode 100644 index 0000000..ffe72ff --- /dev/null +++ b/ui/src/shell/ScrollChip.svelte @@ -0,0 +1,61 @@ + + +{#if show} + +{/if} + + diff --git a/ui/tools/gen-flyout-palettes.cjs b/ui/tools/gen-flyout-palettes.cjs index d24fc2a..c1b1fb9 100644 --- a/ui/tools/gen-flyout-palettes.cjs +++ b/ui/tools/gen-flyout-palettes.cjs @@ -24,10 +24,12 @@ const H = (n) => '0x' + n.toString(16).padStart(6, '0'); const f = (v) => { const s = String(+(+v).toFixed(3)); return (s.includes('.') ? s : s + '.') + 'f'; }; // today's flyout look (MakeTheme in flyout_draw.cpp before #318), the Wind grey rows +// Wind grey dark ON segments: solid teal with a near-black icon, the black-and-teal contrast of the Settings +// switches (Max 2026-10-02, #329); the old dim teal tint did not fit. const GREY = { dark: { menu: 0x000000, card: 0x121212, menub: 0x333333, fg: 0xf2f2f2, fg2: 0xd0d0d0, fg3: 0xb4b6ba, rule: 0x303236, hl: 0x2d2d2d, glyph: 0xb0b0b0, spark: 0x2fbfa5, fill: 0x2fbfa5, fillline: 0, filllineA: 0, lift: 0x0b0b0b, scrim: 0x000000, scrimA: .55, - band: 0x0a0a0a, track: 0x3d3d3d, off: 0x303033, offh: 0x3b3b3f, offic: 0xc8cad0, onic: 0xa9ece0, on: 0x1f5650, onh: 0x266560, + band: 0x0a0a0a, track: 0x3d3d3d, off: 0x303033, offh: 0x3b3b3f, offic: 0xc8cad0, onic: 0x04201b, on: 0x2fbfa5, onh: 0x45cbb3, onb: 0x2fbfa5, onbA: .4, segline: 0x000000, focus: 0xf2f2f2, aurora: .62, tint: 0, tintA: 0 }, }; // Settings control greys (ui/src/design/themes.css, Wind grey): the toggle bar and dropdown match them. From a624a32cc93e1b5344b4e43404567b0267a32abb Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Fri, 2 Oct 2026 23:10:15 +0200 Subject: [PATCH 2/3] fix(tray): solid accent ON segments in every theme (#329) ON toggle segments use the colour of the Settings switches (swon) as a solid fill with the on-accent icon colour: white with a black icon in High contrast, solid orange in Ember, blue in Deep ocean. The old tint over the off fill had too little contrast. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KPUNAWcwghXdHCApcKKjSG --- src/tray_app/flyout_palettes.h | 6 +++--- tests/test_flyout_palettes.cpp | 6 +++--- ui/tools/gen-flyout-palettes.cjs | 11 ++++++----- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/tray_app/flyout_palettes.h b/src/tray_app/flyout_palettes.h index 28a8b10..8c7f84a 100644 --- a/src/tray_app/flyout_palettes.h +++ b/src/tray_app/flyout_palettes.h @@ -30,13 +30,13 @@ inline const FlyoutPalette kPalettes[kPaletteCount] = { { 0x000000, 0x121212, 0x191919, 0xf2f2f2, 0xd0d0d0, 0xb4b6ba, 0x18191a, 0x2d2d2d, 0xb0b0b0, 0x2fbfa5, 0x2fbfa5, 0x000000, 0x0b0b0b, 0x000000, 0x0a0a0a, 0x3d3d3d, 0x0e0e0e, 0x1c1c1c, 0xc8cad0, 0x04201b, 0x2fbfa5, 0x45cbb3, 0x2fbfa5, 0x222222, 0xf2f2f2, 0x000000, 0x222222, 0.f, 0.55f, 0.4f, 0.62f, 0.f } }, { "ember", 10, 8, 6, - { 0x0c0908, 0x130e0b, 0x261d17, 0xf5eee7, 0xd8cbbf, 0xa39382, 0x1f1611, 0x1d1612, 0xb9a898, 0xe48a45, 0xe48a45, 0x000000, 0x130e0b, 0x0c0908, 0x130e0b, 0x3a2e25, 0x18120e, 0x251f1b, 0xb9a898, 0xe9a876, 0x704c31, 0x7e5433, 0xe48a45, 0x3a2c23, 0xe48a45, 0xff8a3d, 0x3a2c23, + { 0x0c0908, 0x130e0b, 0x261d17, 0xf5eee7, 0xd8cbbf, 0xa39382, 0x1f1611, 0x1d1612, 0xb9a898, 0xe48a45, 0xe48a45, 0x000000, 0x130e0b, 0x0c0908, 0x130e0b, 0x3a2e25, 0x18120e, 0x251f1b, 0xb9a898, 0x1c0f06, 0xe48a45, 0xe69658, 0xe48a45, 0x3a2c23, 0xe48a45, 0xff8a3d, 0x3a2c23, 0.f, 0.62f, 0.4f, 0.68f, 0.8f } }, { "ocean", 10, 8, 6, - { 0x0c1117, 0x121821, 0x1f2938, 0xf1f2f4, 0xc6cbd2, 0x9aa2ac, 0x19212e, 0x19222e, 0xb0b6bf, 0x5aaaf5, 0x5aaaf5, 0x000000, 0x121821, 0x0c1117, 0x121821, 0x2d3a4e, 0x161e29, 0x232b35, 0xb0b6bf, 0x87c0f5, 0x3d5d7d, 0x41678c, 0x5aaaf5, 0x263345, 0x5aaaf5, 0x5aaaf5, 0x263345, + { 0x0c1117, 0x121821, 0x1f2938, 0xf1f2f4, 0xc6cbd2, 0x9aa2ac, 0x19212e, 0x19222e, 0xb0b6bf, 0x5aaaf5, 0x5aaaf5, 0x000000, 0x121821, 0x0c1117, 0x121821, 0x2d3a4e, 0x161e29, 0x232b35, 0xb0b6bf, 0x061018, 0x5aaaf5, 0x6cb3f5, 0x5aaaf5, 0x263345, 0x5aaaf5, 0x5aaaf5, 0x263345, 0.f, 0.62f, 0.4f, 0.68f, 0.7f } }, { "hicon", 4, 3, 2, - { 0x000000, 0x000000, 0xbdbdbd, 0xffffff, 0xffffff, 0xe0e0e0, 0x9a9a9a, 0x262626, 0xffffff, 0xffffff, 0xffffff, 0x000000, 0x000000, 0x000000, 0x000000, 0x4a4a4a, 0x000000, 0x0f0f0f, 0xffffff, 0xffffff, 0x707070, 0x828282, 0xffffff, 0xbdbdbd, 0xffffff, 0x000000, 0xbdbdbd, + { 0x000000, 0x000000, 0xbdbdbd, 0xffffff, 0xffffff, 0xe0e0e0, 0x9a9a9a, 0x262626, 0xffffff, 0xffffff, 0xffffff, 0x000000, 0x000000, 0x000000, 0x000000, 0x4a4a4a, 0x000000, 0x0f0f0f, 0xffffff, 0x000000, 0xffffff, 0xffffff, 0xffffff, 0xbdbdbd, 0xffffff, 0x000000, 0xbdbdbd, 0.f, 0.62f, 0.4f, 0.408f, 0.f } }, }; diff --git a/tests/test_flyout_palettes.cpp b/tests/test_flyout_palettes.cpp index 3165d2d..9ecbfe5 100644 --- a/tests/test_flyout_palettes.cpp +++ b/tests/test_flyout_palettes.cpp @@ -63,12 +63,12 @@ TEST_CASE("flyout palettes: radii are sharp for hicon, soft elsewhere") { } } -TEST_CASE("flyout palettes: High contrast dark is white on black with grey on-segments") { +TEST_CASE("flyout palettes: High contrast dark is white on black, ON segments solid white with a black icon (#329)") { const PaletteMode& d = FindPalette("hicon").dark; CHECK(d.menu == 0x000000); CHECK(d.fg == 0xffffff); - CHECK(d.on == 0x707070); - CHECK(d.onic == 0xffffff); + CHECK(d.on == 0xffffff); + CHECK(d.onic == 0x000000); } TEST_CASE("flyout palettes: every palette is fully specified, dark, and on-state differs from off") { diff --git a/ui/tools/gen-flyout-palettes.cjs b/ui/tools/gen-flyout-palettes.cjs index c1b1fb9..c9af0a4 100644 --- a/ui/tools/gen-flyout-palettes.cjs +++ b/ui/tools/gen-flyout-palettes.cjs @@ -1,7 +1,7 @@ // Generates src/tray_app/flyout_palettes.h (the tray flyout's 4 themes, dark only since 0.20.0 / #324) from the same mockup palettes // as ui/tools/gen-themes.cjs, evaluating the tokens tray08.css derives (all color-mix(in srgb) done here): -// toff = fg 12% over card, toffh = fg 18% over card, ton = segon || fill 36% over toff, tonh = segon || fill 44%, -// tonic = segonfg || accent 70% over fg, scrim = bg at 62%, segline = card (the 1px gap shows the group's card). +// off = chip, offh = fg 6% over chip, ton = segon || swon (solid), tonh = segon || fg 12% over swon, +// tonic = segonfg || accenton, scrim = bg at 62%, segline = card (the 1px gap shows the group's card). // // node tools/gen-flyout-palettes.cjs [mockup-ia-dir] // @@ -42,10 +42,11 @@ function mixN(a, b, t) { // a over b by t, 0xRRGGBB numbers } function derive(k) { const card = hex(k.card), fg = hex(k.fg), fill = hex(k.fill), accent = hex(k.pbg); - const toff = mix(fg, card, .12); // base for the ON tint only (unchanged look) const chip = hex(k.chip), chipb = hex(k.chipb), chiph = mix(fg, chip, .06); - const ton = k.segon ? hex(k.segon) : mix(fill, toff, .36), tonh = k.segon ? hex(k.segon) : mix(fill, toff, .44); - const tonic = k.segonfg ? hex(k.segonfg) : mix(accent, fg, .7); + // ON segments: a SOLID fill in the colour of the Settings switches (swon) with the on-accent icon colour, + // so they pop like Wind grey does (Max 2026-10-02, #329); the old tint over the off fill was too faint. + const ton = k.segon ? hex(k.segon) : hex(k.swon), tonh = k.segon ? hex(k.segon) : mix(fg, hex(k.swon), .12); + const tonic = k.segonfg ? hex(k.segonfg) : hex(k.accenton); const fl = k.fillline && k.fillline !== 'transparent'; return { menu: hex(k.bg), card, menub: hex(k.chipb), fg, fg2: hex(k.fg2), fg3: hex(k.fg3), rule: hex(k.line2), hl: hex(k.hover), glyph: hex(k.glyph), spark: accent, fill, fillline: fl ? hex(k.fillline) : 0, filllineA: fl ? 1 : 0, lift: card, From 060957d875faab0f87d19c8b8eb75b24d6528993 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Fri, 2 Oct 2026 23:35:22 +0200 Subject: [PATCH 3/3] feat(tray): default tray menu is performance, Warmth, Brightness and every toggle (#329) A fresh install now shows the performance panel, the Warmth and Brightness sliders, and all four toggle items (Follow the text cursor, Follow keyboard focus, Keep cursor centred, Engine). Defaults only apply where the ini has no tray key yet; explicit choices still win. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KPUNAWcwghXdHCApcKKjSG --- src/tray_items.cpp | 5 +++-- tests/test_flyout.cpp | 2 +- tests/test_tray_items.cpp | 16 +++++++++++----- ui/src/settings-schema.js | 2 +- ui/src/tray/trayModel.js | 4 ++-- ui/tests/tray.spec.js | 38 ++++++++++++++++++++++---------------- 6 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/tray_items.cpp b/src/tray_items.cpp index e812c0d..ca591b4 100644 --- a/src/tray_items.cpp +++ b/src/tray_items.cpp @@ -73,10 +73,11 @@ static std::string Join(const std::vector& items, bool onlyEnabled) { TrayLayout ParseTrayLayout(const IniValues& v) { TrayLayout l; const std::string* p = Find(v, "trayPerf"); - l.perf = p && Trim(*p) == "1"; + l.perf = !p || Trim(*p) == "1"; // shown by default (#329); an explicit 0 hides it l.sliders = ParseList(v, "traySliders", "traySliderOrder", EligibleSliders(), {"colorWarmPct", "colorDimPct"}, kMaxTraySliders); - l.toggles = ParseList(v, "trayToggles", "trayToggleOrder", EligibleToggles(), {}, 0); + // Every toggle item is on by default (#329): follow caret, follow focus, keep centred, engine. + l.toggles = ParseList(v, "trayToggles", "trayToggleOrder", EligibleToggles(), EligibleToggles(), 0); return l; } void WriteTrayLayout(const TrayLayout& l, IniValues& v) { diff --git a/tests/test_flyout.cpp b/tests/test_flyout.cpp index 7aa9862..4b4c335 100644 --- a/tests/test_flyout.cpp +++ b/tests/test_flyout.cpp @@ -272,7 +272,7 @@ TEST_CASE("view: only enabled, known items appear, in layout order") { CHECK(v.toggles[1].key == "keepEdges"); CHECK(v.toggles[1].on); // no align keys = centred = Keep cursor centred ON CHECK(v.profile == L"Work"); - CHECK_FALSE(v.perf); + CHECK(v.perf); // no trayPerf key: shown by default (#329) } TEST_CASE("view: a legacy uiTheme=light ini changes nothing, the flyout is always dark (#324)") { diff --git a/tests/test_tray_items.cpp b/tests/test_tray_items.cpp index 3ee932e..b67f319 100644 --- a/tests/test_tray_items.cpp +++ b/tests/test_tray_items.cpp @@ -11,15 +11,21 @@ static std::vector Keys(const std::vector& v, bool onlyOn } using V = std::vector; -TEST_CASE("tray layout defaults: perf off, warmth and brightness on") { +TEST_CASE("tray layout defaults: perf on, warmth and brightness, every toggle on (#329)") { TrayLayout l = ParseTrayLayout({}); - CHECK_FALSE(l.perf); + CHECK(l.perf); CHECK(Keys(l.sliders, true) == V{"colorWarmPct", "colorDimPct"}); CHECK(Keys(l.sliders, false) == EligibleSliders()); - CHECK(Keys(l.toggles, true).empty()); + CHECK(Keys(l.toggles, true) == EligibleToggles()); CHECK(Keys(l.toggles, false) == EligibleToggles()); } +TEST_CASE("tray layout: an explicit trayPerf=0 and an empty toggle list still hide them (#329)") { + TrayLayout l = ParseTrayLayout({ { "trayPerf", "0" }, { "trayToggles", "" } }); + CHECK_FALSE(l.perf); + CHECK(Keys(l.toggles, true).empty()); +} + TEST_CASE("eligible lists match the owner's choice") { CHECK(EligibleSliders() == V{"colorWarmPct", "colorDimPct", "maxLevel", "zoomInSpeed", "zoomOutSpeed", "panSpeed", "cursorSmoothing", "zoomEaseOutMs"}); @@ -108,10 +114,10 @@ TEST_CASE("toggles are uncapped") { CHECK(Keys(ParseTrayLayout(v).toggles, true).size() == 4); } -TEST_CASE("the engine item exists, defaults off, and round trips") { +TEST_CASE("the engine item exists, defaults on (#329), and round trips") { TrayLayout d = ParseTrayLayout({}); bool found = false; - for (const auto& i : d.toggles) if (i.key == "engine") { found = true; CHECK_FALSE(i.on); } + for (const auto& i : d.toggles) if (i.key == "engine") { found = true; CHECK(i.on); } CHECK(found); IniValues v; v["trayToggles"] = "engine,trackCaret"; diff --git a/ui/src/settings-schema.js b/ui/src/settings-schema.js index 4138d3d..c629af4 100644 --- a/ui/src/settings-schema.js +++ b/ui/src/settings-schema.js @@ -155,7 +155,7 @@ export const groups = [ desc: 'Choose what the tray menu shows, and in what order.', cards: [ { caption: '', rows: [ - { key: 'trayPerf', type: 'toggle', label: 'Performance in the tray', desc: 'Zoom, fps and a frame-time graph.', def: 0 }, + { key: 'trayPerf', type: 'toggle', label: 'Performance in the tray', desc: 'Zoom, fps and a frame-time graph.', def: 1 }, ] }, ] }, diff --git a/ui/src/tray/trayModel.js b/ui/src/tray/trayModel.js index 299c8d8..4529499 100644 --- a/ui/src/tray/trayModel.js +++ b/ui/src/tray/trayModel.js @@ -26,7 +26,7 @@ export const TOGGLES = [ ]; export const KINDS = { sliders: { items: SLIDERS, enabledKey: 'traySliders', orderKey: 'traySliderOrder', defaultOn: ['colorWarmPct', 'colorDimPct'], cap: MAX_SLIDERS }, - toggles: { items: TOGGLES, enabledKey: 'trayToggles', orderKey: 'trayToggleOrder', defaultOn: [], cap: 0 }, + toggles: { items: TOGGLES, enabledKey: 'trayToggles', orderKey: 'trayToggleOrder', defaultOn: ['trackCaret', 'trackFocus', 'keepEdges', 'engine'], cap: 0 }, // all on by default (#329) }; export const TRAY_KEYS = ['trayPerf', 'traySliders', 'traySliderOrder', 'trayToggles', 'trayToggleOrder']; @@ -56,7 +56,7 @@ export function parseList(values, kind) { export function parseTray(values) { return { - perf: String(values.trayPerf ?? '').trim() === '1', + perf: values.trayPerf === undefined || values.trayPerf === null || String(values.trayPerf).trim() === '1', // on by default (#329) sliders: parseList(values, 'sliders'), toggles: parseList(values, 'toggles'), }; diff --git a/ui/tests/tray.spec.js b/ui/tests/tray.spec.js index dcf9432..30f3b19 100644 --- a/ui/tests/tray.spec.js +++ b/ui/tests/tray.spec.js @@ -36,13 +36,16 @@ const chk = (page, list, name) => rowsOf(page, list).filter({ hasText: name }).l // ---- the pure model ---------------------------------------------------------------------------- -test('model: defaults are Warmth and Brightness on, everything else off, performance off', () => { +test('model: defaults are performance on, Warmth and Brightness, and every toggle on (#329)', () => { const t = parseTray({}); - expect(t.perf).toBe(false); + expect(t.perf).toBe(true); expect(t.sliders.map((i) => i.key)).toEqual(SLIDERS.map((i) => i.key)); expect(t.sliders.filter((i) => i.on).map((i) => i.key)).toEqual(['colorWarmPct', 'colorDimPct']); expect(t.toggles.map((i) => i.key)).toEqual(['trackCaret', 'trackFocus', 'keepEdges', 'engine']); - expect(t.toggles.some((i) => i.on)).toBe(false); + expect(t.toggles.every((i) => i.on)).toBe(true); + const off = parseTray({ trayPerf: '0', trayToggles: '' }); // explicit choices still win + expect(off.perf).toBe(false); + expect(off.toggles.some((i) => i.on)).toBe(false); }); test('model: round trip keeps order and enabled, drops unknown keys, appends missing items off', () => { @@ -80,13 +83,13 @@ test('tab: below the sidebar divider, banner, performance card and both lists', await page.locator('.side .it[data-g="tray"]').click(); await expect(page.locator('h1')).toHaveText('Tray menu'); await expect(page.locator('.bdesc')).toHaveText('Choose what the tray menu shows, and in what order.'); - await expect(page.getByRole('switch', { name: 'Performance in the tray' })).not.toBeChecked(); + await expect(page.getByRole('switch', { name: 'Performance in the tray' })).toBeChecked(); // on by default (#329) await expect(page.locator('#cap-sliders')).toHaveText('Sliders'); await expect(page.locator('#cap-toggles')).toHaveText('Toggles'); expect(await names(page, 'sliders')).toEqual(['Warmth', 'Brightness', 'Max zoom', 'Zoom-in speed', 'Zoom-out speed', 'Arrow key speed', 'Pan smoothing', 'Release glide']); expect(await names(page, 'toggles')).toEqual(['Follow the text cursor', 'Follow keyboard focus', 'Keep cursor centred', 'Engine']); await expect(page.locator('[data-cnt="sliders"]')).toHaveText('2 of 4'); - await expect(page.locator('[data-cnt="toggles"]')).toHaveText('0 on'); + await expect(page.locator('[data-cnt="toggles"]')).toHaveText('4 on'); // every toggle on by default (#329) await expect(chk(page, 'sliders', 'Warmth')).toHaveAttribute('aria-checked', 'true'); await expect(chk(page, 'sliders', 'Max zoom')).toHaveAttribute('aria-checked', 'false'); }); @@ -102,10 +105,11 @@ test('tab: item icons are bare line icons without a background box', async ({ pa test('performance switch writes trayPerf', async ({ page }) => { await open(page); - await page.getByRole('switch', { name: 'Performance in the tray' }).check({ force: true }); - expect((await live(page)).trayPerf).toBe('1'); + // On by default (#329): turning it off writes 0, back on writes 1. await page.getByRole('switch', { name: 'Performance in the tray' }).uncheck({ force: true }); expect((await live(page)).trayPerf).toBe('0'); + await page.getByRole('switch', { name: 'Performance in the tray' }).check({ force: true }); + expect((await live(page)).trayPerf).toBe('1'); }); test('check and uncheck write the enabled list and the full order', async ({ page }) => { @@ -119,11 +123,11 @@ test('check and uncheck write the enabled list and the full order', async ({ pag v = await live(page); expect(v.traySliders).toBe('colorDimPct,panSpeed'); // a click anywhere on the row toggles it too - await rowsOf(page, 'toggles').filter({ hasText: 'Follow keyboard focus' }).locator('.k').click(); + await rowsOf(page, 'toggles').filter({ hasText: 'Follow keyboard focus' }).locator('.k').click(); // on by default: turns it off v = await live(page); - expect(v.trayToggles).toBe('trackFocus'); + expect(v.trayToggles).toBe('trackCaret,keepEdges,engine'); expect(v.trayToggleOrder).toBe('trackCaret,trackFocus,keepEdges,engine'); - await expect(page.locator('[data-cnt="toggles"]')).toHaveText('1 on'); + await expect(page.locator('[data-cnt="toggles"]')).toHaveText('3 on'); }); test('slider cap: at four the unchecked boxes are disabled with a note, unchecking frees them', async ({ page }) => { @@ -138,8 +142,10 @@ test('slider cap: at four the unchecked boxes are disabled with a note, unchecki await rowsOf(page, 'sliders').filter({ hasText: 'Zoom-in speed' }).locator('.k').click(); // row click is refused too expect((await live(page)).traySliders).toBe(before); // toggles are not capped - for (const n of ['Follow the text cursor', 'Follow keyboard focus', 'Keep cursor centred']) await chk(page, 'toggles', n).click(); - await expect(page.locator('[data-cnt="toggles"]')).toHaveText('3 on'); + for (const n of ['Follow the text cursor', 'Follow keyboard focus', 'Keep cursor centred']) await chk(page, 'toggles', n).click(); // all on by default: three off + await expect(page.locator('[data-cnt="toggles"]')).toHaveText('1 on'); + for (const n of ['Follow the text cursor', 'Follow keyboard focus', 'Keep cursor centred']) await chk(page, 'toggles', n).click(); // and back on: no cap + await expect(page.locator('[data-cnt="toggles"]')).toHaveText('4 on'); await chk(page, 'sliders', 'Warmth').click(); await expect(page.locator('[data-cnt="sliders"]')).toHaveText('3 of 4'); await expect(chk(page, 'sliders', 'Zoom-in speed')).toBeEnabled(); @@ -237,17 +243,17 @@ test('search finds the Performance row and jumps to the tray tab', async ({ page await expect(page.locator('h1')).toHaveText('Tray menu'); }); -test('engine: the Engine item is off by default, has an icon and a description, and writes trayToggles', async ({ page }) => { +test('engine: the Engine item is on by default (#329), has an icon and a description, and writes trayToggles', async ({ page }) => { await open(page); const row = rowsOf(page, 'toggles').filter({ hasText: 'Engine' }); await expect(row.locator('.tic svg')).toHaveCount(1); await expect(row.locator('.d')).not.toBeEmpty(); - await expect(chk(page, 'toggles', 'Engine')).toHaveAttribute('aria-checked', 'false'); + await expect(chk(page, 'toggles', 'Engine')).toHaveAttribute('aria-checked', 'true'); await chk(page, 'toggles', 'Engine').click(); const v = await live(page); - expect(v.trayToggles).toBe('engine'); + expect(v.trayToggles).toBe('trackCaret,trackFocus,keepEdges'); expect(v.trayToggleOrder).toBe('trackCaret,trackFocus,keepEdges,engine'); - await expect(page.locator('[data-cnt="toggles"]')).toHaveText('1 on'); + await expect(page.locator('[data-cnt="toggles"]')).toHaveText('3 on'); }); test('removed tools: Mouse lock, Pass keys and Pause Wind are gone, and old ini keys for them are dropped', async ({ page }) => {