Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/tray_app/flyout_palettes.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ 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,
{ 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 } },
};

Expand Down
5 changes: 3 additions & 2 deletions src/tray_items.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ static std::string Join(const std::vector<TrayItem>& 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) {
Expand Down
4 changes: 2 additions & 2 deletions src/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#define WIND_VER_MAJOR 0
#define WIND_VER_MINOR 20
#define WIND_VER_PATCH 1
#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.1"
#define WIND_VERSION_STR "0.20.2"
2 changes: 1 addition & 1 deletion tests/test_flyout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)") {
Expand Down
9 changes: 5 additions & 4 deletions tests/test_flyout_palettes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -62,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") {
Expand Down
16 changes: 11 additions & 5 deletions tests/test_tray_items.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,21 @@ static std::vector<std::string> Keys(const std::vector<TrayItem>& v, bool onlyOn
}
using V = std::vector<std::string>;

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"});
Expand Down Expand Up @@ -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";
Expand Down
6 changes: 5 additions & 1 deletion ui/src/Settings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -317,6 +318,7 @@
{/key}
<div class="tail"></div>
</main>
<ScrollChip target={main} />
</div>
<SaveCapsule {count} onSave={save} onDiscard={discard} />
<div class="sr-only" role="status" aria-live="polite" aria-atomic="true">{announcement}</div>
Expand Down Expand Up @@ -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; } }
Expand Down
2 changes: 1 addition & 1 deletion ui/src/controls/Preview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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.1',
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',
Expand Down
2 changes: 1 addition & 1 deletion ui/src/settings-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
] },
] },

Expand Down
61 changes: 61 additions & 0 deletions ui/src/shell/ScrollChip.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<script>
// The Settings page's scroll indicator (#329, Max 2026-10-02): one small fixed-size chip on the right
// edge instead of a scrollbar. It shows only when the page has more to scroll, its position follows
// the scroll, and it can be dragged. The page itself hides its native scrollbar (Settings.svelte).
let { target } = $props();
const CHIP = 36, PAD = 6; // chip height and the gap it keeps from the top and bottom, px
let show = $state(false), top = $state(0), dragging = $state(false);

function update() {
const el = target;
if (!el) return;
const max = el.scrollHeight - el.clientHeight;
show = max > 1;
if (!show) return;
const track = el.clientHeight - PAD * 2 - CHIP;
top = PAD + (el.scrollTop / max) * Math.max(0, track);
}

$effect(() => {
const el = target;
if (!el) return;
update();
el.addEventListener('scroll', update, { passive: true });
const ro = new ResizeObserver(update);
ro.observe(el);
// Page switches, advanced rows and search results change the content height without a resize.
const mo = new MutationObserver(update);
mo.observe(el, { childList: true, subtree: true });
return () => { el.removeEventListener('scroll', update); ro.disconnect(); mo.disconnect(); };
});

function down(e) {
const el = target;
if (!el) return;
e.preventDefault();
dragging = true;
const startY = e.clientY, startTop = el.scrollTop;
const max = el.scrollHeight - el.clientHeight;
const track = Math.max(1, el.clientHeight - PAD * 2 - CHIP);
const move = (ev) => { el.scrollTop = startTop + (ev.clientY - startY) * (max / track); };
const up = () => {
dragging = false;
window.removeEventListener('pointermove', move);
window.removeEventListener('pointerup', up);
};
window.addEventListener('pointermove', move);
window.addEventListener('pointerup', up);
}
</script>

{#if show}
<div class="chip" class:dragging style:top="{top}px" style:height="{CHIP}px" onpointerdown={down} aria-hidden="true"></div>
{/if}

<style>
.chip { position: absolute; right: 4px; width: 4px; border-radius: 99px; z-index: 5;
background: color-mix(in srgb, var(--fg) 22%, transparent);
transition: width var(--dur-fast) var(--ease), right var(--dur-fast) var(--ease),
background-color var(--dur-fast) var(--ease); }
.chip:hover, .chip.dragging { width: 6px; right: 3px; background: color-mix(in srgb, var(--fg) 36%, transparent); }
</style>
4 changes: 2 additions & 2 deletions ui/src/tray/trayModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'];

Expand Down Expand Up @@ -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'),
};
Expand Down
38 changes: 22 additions & 16 deletions ui/tests/tray.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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');
});
Expand All @@ -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 }) => {
Expand All @@ -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 }) => {
Expand All @@ -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();
Expand Down Expand Up @@ -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 }) => {
Expand Down
Loading
Loading