// Navigation+ (src/areas/navigation): the reference area. Its hide and layout rules, Focus Mode // and Keyboard Plus as feature modules, compared with the plugins they replace. The table-level // checks (every module starts and stops clean, one observer, the budget) are in areas.test.ts. import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import test from 'node:test'; import { NAVIGATION_CSS, navigationCss } from '../src/areas/navigation/css.ts'; import { FEATURES } from '../src/areas/navigation/features.ts'; import { FOCUS_SCHEMA, focusCss, type FocusSettings } from '../src/areas/navigation/focusMode.ts'; import { MODULES } from '../src/areas/navigation/index.ts'; import { keyboardActions } from '../src/areas/navigation/keyboardPlus.ts'; import type { PluginApi } from '../src/inject/pluginApi.ts'; import { normalizeSettings } from '../../../waybetter/packages/runtime/src/settingsForm.ts'; import { areaPage, flagsOnly, messages } from './helpers/areaPage.ts'; const BUNDLED = new URL('../src-tauri/bundled-plugins/', import.meta.url); /** The old plugin, loaded the way native code loads it, to compare the port against. */ function oldPlugin(file: string): Record { const module = { exports: {} as unknown }; new Function('module', 'exports', readFileSync(new URL(file, BUNDLED), 'utf8'))(module, module.exports); return module.exports as Record; } test('Navigation+: its fourteen features are modules, and each hide rule only names its own part of X', () => { assert.equal(FEATURES.length, 14); assert.deepEqual(MODULES.map(module => module.id).sort(), FEATURES.map(feature => feature.id).sort()); assert.equal(Object.keys(NAVIGATION_CSS).length, 12); const markers: Record = { hidePremiumNav: '/i/verified', hideChatNav: '/i/chat', hideCommunitiesNav: '/communities', hideGrokNav: 'a[href="/i/grok"]', hideSidebarTrends: '[data-testid="trend"]', hideWhoToFollow: 'UserCell', hideLiveOnX: 'Live on X', hideTodaysNews: 'news_sidebar', hidePremiumUpsells: 'premium_sign_up', hideRelevantPeople: 'data-bt-route="status"', hideSidebar: '[data-testid="sidebarColumn"] {', fullWidthTimeline: 'flex-grow: 1', }; for (const [flag, marker] of Object.entries(markers)) { const css = navigationCss({ [flag]: true }); assert.ok(css.includes(marker), flag); // No :has() inside :has() (invalid CSS: the whole rule would be dropped). assert.doesNotMatch(css, /:has\([^)]*:has\(/, flag); } assert.equal(navigationCss({}), ''); // The rail rules never reach into posts, and the right-column ones stay in the right column. for (const flag of ['hidePremiumNav', 'hideChatNav', 'hideCommunitiesNav', 'hideGrokNav'] as const) assert.doesNotMatch(NAVIGATION_CSS[flag](), /article|tweet|sidebarColumn/, flag); assert.doesNotMatch(NAVIGATION_CSS.hideSidebarTrends(), /(^|,\n)section\[role="region"\]/, 'Explore\'s own trends are Search+\'s'); }); test('Navigation+: "Relevant people" only runs on post pages, and nothing of it on chat', () => { const env = areaPage('/home'); env.registry.setFlags(flagsOnly({ hideRelevantPeople: true, hideSidebarTrends: true })); assert.deepEqual(env.registry.active(), ['hideSidebarTrends']); env.go('/someone/status/1'); assert.deepEqual(env.registry.active().sort(), ['hideRelevantPeople', 'hideSidebarTrends']); assert.deepEqual(env.live(), ['wbf-hideRelevantPeople', 'wbf-hideSidebarTrends']); env.go('/i/chat'); assert.deepEqual(env.registry.active(), []); env.registry.dispose(); }); test('Focus Mode: the same settings and the same rules as the plugin it replaces', () => { const Old = oldPlugin('focus-mode.plugin.js') as unknown as { DEFAULTS: FocusSettings; normalize(saved: unknown): FocusSettings; css(settings: FocusSettings): string }; assert.deepEqual(normalizeSettings(FOCUS_SCHEMA, null), Old.DEFAULTS, 'someone who enabled the plugin and never opened its settings sees no change'); for (const saved of [{ metrics: false, trends: 'yes', extra: true }, { enabled: false }, { sidebar: true, followCounts: true }]) { assert.deepEqual(normalizeSettings(FOCUS_SCHEMA, saved), Old.normalize(saved), JSON.stringify(saved)); } const keys = Object.keys(Old.DEFAULTS) as (keyof FocusSettings)[]; // Every combination of the six switches gives byte-identical CSS. for (let bits = 0; bits < 2 ** keys.length; bits += 1) { const settings = Object.fromEntries(keys.map((key, index) => [key, !!(bits & (1 << index))])) as FocusSettings; assert.equal(focusCss(settings), Old.css(settings), JSON.stringify(settings)); } assert.equal(focusCss({ ...Old.DEFAULTS, enabled: false }), '', 'off: nothing hidden'); // Its words are the plugin's own keys (`words: 'focus-mode'`): every label and hint of the form exists. for (const field of FOCUS_SCHEMA) if (field.type !== 'divider') { assert.ok(messages[`plugin.focus-mode.${field.label}`], field.label); if (field.hint) assert.ok(messages[`plugin.focus-mode.${field.hint}`], field.hint); } }); test('Focus Mode: reads the plugin\'s stored settings, toggles with its command and Alt+Shift+F, saves where the plugin saved', () => { const env = areaPage(); env.store.set('bt-plugin:focus-mode:settings', JSON.stringify({ enabled: true, metrics: false, trends: true, whoToFollow: false, followCounts: false, sidebar: false })); env.registry.setFlags(flagsOnly({ 'navigation.focusMode': true })); assert.deepEqual(env.live(), ['wbf-navigation.focusMode']); assert.deepEqual(env.api.commands.list().map(command => command.id).sort(), ['bt-navigation-plus.plugin.js:focusMode.toggle', 'bt-navigation-plus.plugin.js:settings']); assert.equal(env.api.commands.list().find(command => command.id.endsWith('focusMode.toggle'))!.title, 'Toggle Focus Mode'); assert.equal(env.api.commands.list().find(command => command.id.endsWith(':settings'))!.title, 'Navigation+ settings'); // (The confirming toast needs a real DOM; here the kit reports that it cannot draw, which is fine.) const original = console.error; console.error = () => {}; try { // Alt+Shift+F is the command's shortcut now, so the palette lists it; it still works from the page. let prevented = false; env.doc.emit('keydown', { key: 'F', code: 'KeyF', altKey: true, shiftKey: true, ctrlKey: false, metaKey: false, target: null, preventDefault() { prevented = true; }, stopPropagation() {} }); assert.ok(prevented); assert.equal(JSON.parse(env.store.get('bt-plugin:focus-mode:settings')!).enabled, false); assert.deepEqual(env.live(), [], 'off: no rule'); env.api.commands.run('bt-navigation-plus.plugin.js:focusMode.toggle'); assert.deepEqual(JSON.parse(env.store.get('bt-plugin:focus-mode:settings')!), { enabled: true, metrics: false, trends: true, whoToFollow: false, followCounts: false, sidebar: false }); assert.deepEqual(env.live(), ['wbf-navigation.focusMode']); } finally { console.error = original; } env.registry.dispose(); }); test('Keyboard Plus: keys act on the focused post, copy or navigate, and never click an action', async () => { const clicked: string[] = []; const copied: string[] = []; const said: string[] = []; const node = (name: string) => ({ click() { clicked.push(name); }, closest: () => null }); const article = { contains: () => false, querySelector: (selector: string) => (selector.includes('User-Name') ? node('author') : null), querySelectorAll: (selector: string) => (selector.includes('/photo/') ? [node('photo')] : []), } as unknown as Element; const tweet = { id: '1', url: 'https://x.com/someone/status/1', handle: 'someone', name: 'Someone', text: 'hello', time: null, media: [], element: article }; const api = { tweet: () => tweet, copyText: async (text: string) => { copied.push(text); return true; }, toast: (message: string) => { said.push(message); } } as unknown as PluginApi; const actions = keyboardActions({ api, text: key => messages[`plugin.keyboard-plus.${key}`] ?? key, win: { location: { assign() {} } } as unknown as Window & typeof globalThis }); assert.deepEqual(Object.keys(actions), ['c', 'C', 'v', 'a', 'q']); await actions.c!(article); await actions.C!(article); actions.v!(article); actions.a!(article); actions.q!(article); assert.deepEqual(copied, ['https://x.com/someone/status/1', 'hello']); assert.deepEqual(clicked, ['photo', 'author']); assert.deepEqual(said, ['Link copied', 'Text copied', 'No quoted post']); // In the page: a key without a focused post does nothing. const env = areaPage(); env.registry.setFlags(flagsOnly({ 'navigation.keyboardPlus': true })); let prevented = 0; env.doc.emit('keydown', { key: 'c', target: null, preventDefault() { prevented += 1; }, stopPropagation() {} }); assert.equal(prevented, 0, 'no post is focused'); env.registry.dispose(); }); /** Selector specificity [ids, classes/attributes/pseudo-classes, types] of one complex selector * (enough for the rules here: :is/:not/:has count their most specific argument, :where nothing). */ function specificity(selector: string): [number, number, number] { const total: [number, number, number] = [0, 0, 0]; const add = (s: [number, number, number]) => { total[0] += s[0]; total[1] += s[1]; total[2] += s[2]; }; let rest = selector; for (;;) { const match = /:(is|not|has|where)\(/.exec(rest); if (!match) break; let depth = 1; let i = match.index + match[0].length; for (; i < rest.length && depth; i++) { if (rest[i] === '(') depth++; else if (rest[i] === ')') depth--; } const inner = rest.slice(match.index + match[0].length, i - 1); if (match[1] !== 'where') { const args = inner.split(/,(?![^(]*\))/).map(arg => specificity(arg.trim())); add(args.reduce((best, s) => (s[0] - best[0] || s[1] - best[1] || s[2] - best[2]) > 0 ? s : best)); } rest = rest.slice(0, match.index) + ' ' + rest.slice(i); } rest = rest.replace(/\[[^\]]*\]/g, () => { total[1]++; return ' '; }); total[0] += (rest.match(/#[\w-]+/g) ?? []).length; total[1] += (rest.match(/\.[\w-]+|:[\w-]+/g) ?? []).length; total[2] += (rest.replace(/[#.:][\w-]+/g, ' ').match(/(^|[\s>+~])[a-z][\w-]*/gi) ?? []).length; return total; } const beats = (a: [number, number, number], b: [number, number, number]) => (a[0] - b[0] || a[1] - b[1] || a[2] - b[2]) > 0; test('Navigation+: full-width timeline wins over a theme with a layout of its own', () => { // The Classic Twitter theme's layout rules as the theme engine served them on live x.com // (2026-10-01): both !important, so only specificity decides against ours. const theme = [ '[data-testid="primaryColumn"]:is(.r-1ye8kvj)', 'main[role="main"] > div:is(.r-113js5t):has([data-testid="primaryColumn"]:is(.r-1ye8kvj)):has([data-testid="sidebarColumn"]:is(.r-1hycxz))', 'main[role="main"] > div:is(.r-33ulu8):has([data-testid="primaryColumn"]:is(.r-1ye8kvj)):not(:has([data-testid="sidebarColumn"]:is(.r-1hycxz)))', ].map(specificity); assert.deepEqual(specificity('[data-testid="primaryColumn"]:is(.r-1ye8kvj)'), [0, 2, 0]); const css = NAVIGATION_CSS.fullWidthTimeline(); const selectors = [...css.matchAll(/^([^{}\n]+)\{/gm)].flatMap(match => match[1]!.split(/,(?![^(]*\))/).map(item => item.trim())); assert.equal(selectors.length, 3); for (const selector of selectors) for (const rule of theme) assert.ok(beats(specificity(selector), rule), `${selector} outranks ${rule.join(',')}`); // The id only lifts the rules: no element carries it, and the chat row stays excluded. assert.match(css, /#bt-navigation-full-width/); assert.equal((css.match(/dm-container/g) ?? []).length, 2); assert.doesNotMatch(css, /:has\([^)]*:has\(/); }); test('Navigation+: Today\'s News goes with its card (live 2026-10-01: the testid is only the card\'s empty tracker)', () => { // Live shape of the right column's news card:
(an empty // impression tracker)
heading "Today's News", the stories
, next to an NFL card. // Hiding only the testid left the heading and the stories on screen. const css = NAVIGATION_CSS.hideTodaysNews(); const selectors = css.replace(/\{[^}]*\}/, '').split(/,\n/).map(item => item.trim()); assert.ok(selectors.includes('[data-testid="sidebarColumn"] div:has(> [data-testid="news_sidebar"])'), 'the card holding the tracker as a direct child'); // Only the card: every selector names the tracker, and the card one only as a direct child in the right column. for (const selector of selectors) assert.match(selector, /news_sidebar/, selector); assert.doesNotMatch(css, /div:has\([^>]*\[data-testid="news_sidebar"\]\)/, 'never "any div that contains it somewhere" (that would take the whole column)'); assert.doesNotMatch(css, /:has\([^)]*:has\(/); });