Simplify extension controls and add options page#19
Simplify extension controls and add options page#19ArsalaBangash wants to merge 2 commits intomainfrom
Conversation
📝 WalkthroughWalkthroughAdds an options/settings page and X site support, simplifies popup to a compact status view, reduces focus modes from three to two (removes CustomFocus), removes several storage-driven behaviors, and refactors website/controller logic (LinkedIn/X controllers enhanced for panel/sidebar concealment). Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
README.md (1)
34-34:⚠️ Potential issue | 🟡 MinorIncorrect output folder in installation instructions.
The build script outputs to
extension-build, but the README instructs users to select thedistfolder. This will cause the extension to fail to load.📝 Proposed fix
-5. Click "Load unpacked" and select the `dist` folder +5. Click "Load unpacked" and select the `extension-build` folder🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 34, Update the README installation step that currently says to select the `dist` folder: change the instruction to tell users to click "Load unpacked" and select the `extension-build` folder (the actual output of the build script) so the extension loads correctly; locate and update the line that reads `Click "Load unpacked" and select the \`dist\` folder` to reference `extension-build` instead.
🧹 Nitpick comments (10)
src/ts/quotes/index.ts (1)
25-46: Consider removing theasynckeyword since the function no longer awaits anything.The
createQuoteElementfunction is declaredasyncbut contains noawaitcalls after the removal of storage-based font size lookups. The function can be simplified to returnHTMLDivElementdirectly.♻️ Suggested simplification
-const createQuoteElement = async (): Promise<HTMLDivElement> => { +const createQuoteElement = (): HTMLDivElement => { const quote = getRandomQuote(); const quoteDiv = document.createElement('div');Note: This would also require updating callers that currently
awaitthis function, and updating the corresponding test'sbeforeEachto be synchronous.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ts/quotes/index.ts` around lines 25 - 46, The createQuoteElement function is marked async but does not await anything; remove the async keyword and change its return type from Promise<HTMLDivElement> to HTMLDivElement in the function signature (createQuoteElement) so it returns the div synchronously, then update any callers/tests that currently use await createQuoteElement() (and the related beforeEach) to call it synchronously and adjust tests to be non-async.src/options/options.ts (1)
1-1: Placeholder module noted.This empty export creates a valid ES module entry point for the build system. Consider adding a TODO comment if this is intended to be expanded with options page logic in the future.
💡 Optional: Add intent comment
+// TODO: Add options page logic (e.g., settings persistence, UI interactions) export {};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/options/options.ts` at line 1, This module currently only exports an empty object (export {}), acting as a placeholder; add a short TODO/intent comment at the top of the file indicating that this is an intentional placeholder for future options page logic (e.g., "TODO: implement options page logic" or "Placeholder module for options exports") so maintainers know the empty export is deliberate.src/ts/websites/linkedin/linkedin-controller.ts (1)
129-136: Consider extractingrestoreInlineStyleto the base class or a shared utility.This helper function could be reused by other controllers (e.g.,
XController) if they adopt similar concealment patterns in the future. Currently it's a module-level function, which works but limits reusability.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ts/websites/linkedin/linkedin-controller.ts` around lines 129 - 136, The module-level helper restoreInlineStyle should be moved to a shared location so other controllers can reuse it; extract restoreInlineStyle into a shared utility module (or into the common/base controller class used by LinkedInController and future XController) and replace the local function with an import or base-class method call; update LinkedInController to call the new shared function or base method (preserving the same signature restoreInlineStyle(element: HTMLElement, property: string, value: string): void) and adjust other controllers to reuse it to avoid duplication.src/ts/websites/linkedin/linkedin-controller.test.ts (1)
4-12: Mock may be incomplete forhasFeedLoadedandisFeedHidden.The
LinkedInUtilsmock doesn't includehasFeedLoadedorisFeedHidden, which are referenced byLinkedInController.focus()and the feed distraction setup. Whileunfocus()doesn't directly call these (it callssetFeedVisibility(true)which usesshowFeed()), adding these mocks would make the mock more complete and prevent issues if test coverage expands.💡 More complete mock
jest.mock('./linkedin-utils', () => ({ __esModule: true, default: { isHomePage: jest.fn(), getLinkedInFeed: jest.fn(), getLinkedInPanels: jest.fn(), arePanelsHidden: jest.fn(), + hasFeedLoaded: jest.fn(), + isFeedHidden: jest.fn(), }, }))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ts/websites/linkedin/linkedin-controller.test.ts` around lines 4 - 12, The LinkedInUtils mock is missing hasFeedLoaded and isFeedHidden which LinkedInController.focus() and feed visibility logic reference; update the jest.mock for LinkedInUtils to include hasFeedLoaded: jest.fn() and isFeedHidden: jest.fn() (add them alongside isHomePage, getLinkedInFeed, getLinkedInPanels, arePanelsHidden) so tests can stub/override their return values when exercising LinkedInController.focus(), unfocus(), setFeedVisibility(true) and showFeed().src/ts/websites/x/x-controller.test.ts (2)
20-39: Consider adding an assertion for feed visibility restoration.The test name states "while restoring the feed" but only asserts the sidebar state. Adding an assertion for the feed element's visibility would make the test more comprehensive and match its description.
💡 Suggested improvement
const controller = new XController() controller.unfocus() expect(sidebar.style.display).toBe('none') + // Verify feed was restored (not hidden) + expect(feed.style.display).not.toBe('none') })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ts/websites/x/x-controller.test.ts` around lines 20 - 39, The test currently asserts only that the sidebar remains hidden after controller.unfocus; add an assertion that the feed element returned by XUtils.getXFeed (the feed variable) is restored to visible. After calling controller.unfocus() in the test, add an assertion such as expecting feed.style.display not to be 'none' (or to equal the expected visible value), so the test name "while restoring the feed" matches the verification of XController restoring the feed's visibility.
24-27: Consider cleaning updocument.URLmodification inafterEach.Modifying
document.URLwithObject.definePropertypersists across tests if not reset. This could cause flaky behavior if more tests are added.💡 Suggested cleanup
describe('XController', () => { + const originalURL = document.URL + beforeEach(() => { jest.clearAllMocks() }) + + afterEach(() => { + Object.defineProperty(document, 'URL', { + value: originalURL, + writable: true, + }) + })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ts/websites/x/x-controller.test.ts` around lines 24 - 27, The test mutates document.URL via Object.defineProperty in x-controller.test.ts and doesn't restore it; update the test to capture the original value before modifying (cache original = document.URL), apply Object.defineProperty(document, 'URL', { value: 'https://x.com/home', writable: true }) in the test setup, and add/modify an afterEach hook to restore document.URL by re-applying Object.defineProperty(document, 'URL', { value: original, writable: true }) (use the existing afterEach or add one if missing) so the mutation does not leak between tests.src/ts/background.ts (1)
25-31: Theget()result is unused; consider simplifying.The
get(['appState'])call retrieves stored data but the callback ignores it, immediately overwriting with the baseline. If the intent is simply to reset state on worker restart,set()alone suffices.💡 Simplified initialization
// In MV3 the worker restarts frequently. Reset appState on startup so each // worker begins from the same focused baseline. -browser.storage.local.get(['appState']).then(() => { - browser.storage.local.set({ - appState: appState, - }) +browser.storage.local.set({ + appState: appState, })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ts/background.ts` around lines 25 - 31, The storage initialization calls browser.storage.local.get(['appState']) but ignores its result and immediately overwrites the value; remove the unused get call and call browser.storage.local.set({ appState: appState }) directly so the worker simply resets state on startup—update the code that references browser.storage.local.get and browser.storage.local.set and leave the appState variable usage intact.src/options/options.html (1)
2-6: Addlangattribute and move charset meta for better practices.Consider adding the
langattribute to the<html>element for accessibility (screen readers) and moving the charset meta to be first in<head>for proper encoding detection.♻️ Suggested improvements
<!DOCTYPE html> -<html> +<html lang="en"> <head> + <meta charset="UTF-8" /> <title>Focused Browsing Settings</title> <link rel="stylesheet" type="text/css" href="options.css" /> - <meta charset="UTF-8" /> </head>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/options/options.html` around lines 2 - 6, Update the <html> element to include a lang attribute (e.g., lang="en") and move the <meta charset="UTF-8" /> element to be the very first child inside the <head> so encoding is detected early; locate these changes around the existing <html> and <head> tags in options.html and adjust accordingly.src/popup/popup.ts (1)
7-9: Consider adding error handling foropenOptionsPage.If
openOptionsPage()fails (e.g., options page not found), the rejected promise is unhandled. While rare, adding a catch or removing the unnecessaryawaitwould be cleaner.♻️ Option 1: Remove unnecessary await
openOptionsButton.addEventListener('click', async () => { - await browser.runtime.openOptionsPage(); + browser.runtime.openOptionsPage(); });♻️ Option 2: Add error handling
openOptionsButton.addEventListener('click', async () => { - await browser.runtime.openOptionsPage(); + try { + await browser.runtime.openOptionsPage(); + } catch (err) { + console.error('Failed to open options page:', err); + } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/popup/popup.ts` around lines 7 - 9, The click handler for openOptionsButton currently awaits browser.runtime.openOptionsPage() without error handling; either remove the unnecessary await to let the promise resolve naturally or wrap the call in a try/catch and handle failures (e.g., log the error via console.error or show a user-friendly message). Locate the event listener on openOptionsButton and update it to either call browser.runtime.openOptionsPage() without await or surround the await browser.runtime.openOptionsPage() with try { ... } catch (err) { console.error('Failed to open options page', err); } so rejected promises are handled.src/popup/popup.css (1)
113-117: Reconsider removing outline on focus-visible for accessibility.Setting
outline: noneon:focus-visibleremoves the default focus indicator for keyboard users. While the color change provides some feedback, consider adding a visible focus ring or underline for better keyboard navigation accessibility.♿ Proposed fix to improve focus visibility
.options-button:hover, .options-button:focus-visible { color: var(--ink); - outline: none; + outline: 2px solid var(--accent); + outline-offset: 2px; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/popup/popup.css` around lines 113 - 117, The CSS rule for .options-button:focus-visible currently removes the browser focus indicator (outline: none), harming keyboard accessibility; update the .options-button:hover and .options-button:focus-visible rules to keep hover styling but remove the outline:none and instead add a visible focus style for .options-button:focus-visible (e.g., a clearly contrasting outline or focus ring using outline or box-shadow tied to a CSS variable like --focus or --ink) so keyboard users get a high-contrast, visible indicator while preserving the existing color change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@README.md`:
- Line 34: Update the README installation step that currently says to select the
`dist` folder: change the instruction to tell users to click "Load unpacked" and
select the `extension-build` folder (the actual output of the build script) so
the extension loads correctly; locate and update the line that reads `Click
"Load unpacked" and select the \`dist\` folder` to reference `extension-build`
instead.
---
Nitpick comments:
In `@src/options/options.html`:
- Around line 2-6: Update the <html> element to include a lang attribute (e.g.,
lang="en") and move the <meta charset="UTF-8" /> element to be the very first
child inside the <head> so encoding is detected early; locate these changes
around the existing <html> and <head> tags in options.html and adjust
accordingly.
In `@src/options/options.ts`:
- Line 1: This module currently only exports an empty object (export {}), acting
as a placeholder; add a short TODO/intent comment at the top of the file
indicating that this is an intentional placeholder for future options page logic
(e.g., "TODO: implement options page logic" or "Placeholder module for options
exports") so maintainers know the empty export is deliberate.
In `@src/popup/popup.css`:
- Around line 113-117: The CSS rule for .options-button:focus-visible currently
removes the browser focus indicator (outline: none), harming keyboard
accessibility; update the .options-button:hover and
.options-button:focus-visible rules to keep hover styling but remove the
outline:none and instead add a visible focus style for
.options-button:focus-visible (e.g., a clearly contrasting outline or focus ring
using outline or box-shadow tied to a CSS variable like --focus or --ink) so
keyboard users get a high-contrast, visible indicator while preserving the
existing color change.
In `@src/popup/popup.ts`:
- Around line 7-9: The click handler for openOptionsButton currently awaits
browser.runtime.openOptionsPage() without error handling; either remove the
unnecessary await to let the promise resolve naturally or wrap the call in a
try/catch and handle failures (e.g., log the error via console.error or show a
user-friendly message). Locate the event listener on openOptionsButton and
update it to either call browser.runtime.openOptionsPage() without await or
surround the await browser.runtime.openOptionsPage() with try { ... } catch
(err) { console.error('Failed to open options page', err); } so rejected
promises are handled.
In `@src/ts/background.ts`:
- Around line 25-31: The storage initialization calls
browser.storage.local.get(['appState']) but ignores its result and immediately
overwrites the value; remove the unused get call and call
browser.storage.local.set({ appState: appState }) directly so the worker simply
resets state on startup—update the code that references
browser.storage.local.get and browser.storage.local.set and leave the appState
variable usage intact.
In `@src/ts/quotes/index.ts`:
- Around line 25-46: The createQuoteElement function is marked async but does
not await anything; remove the async keyword and change its return type from
Promise<HTMLDivElement> to HTMLDivElement in the function signature
(createQuoteElement) so it returns the div synchronously, then update any
callers/tests that currently use await createQuoteElement() (and the related
beforeEach) to call it synchronously and adjust tests to be non-async.
In `@src/ts/websites/linkedin/linkedin-controller.test.ts`:
- Around line 4-12: The LinkedInUtils mock is missing hasFeedLoaded and
isFeedHidden which LinkedInController.focus() and feed visibility logic
reference; update the jest.mock for LinkedInUtils to include hasFeedLoaded:
jest.fn() and isFeedHidden: jest.fn() (add them alongside isHomePage,
getLinkedInFeed, getLinkedInPanels, arePanelsHidden) so tests can stub/override
their return values when exercising LinkedInController.focus(), unfocus(),
setFeedVisibility(true) and showFeed().
In `@src/ts/websites/linkedin/linkedin-controller.ts`:
- Around line 129-136: The module-level helper restoreInlineStyle should be
moved to a shared location so other controllers can reuse it; extract
restoreInlineStyle into a shared utility module (or into the common/base
controller class used by LinkedInController and future XController) and replace
the local function with an import or base-class method call; update
LinkedInController to call the new shared function or base method (preserving
the same signature restoreInlineStyle(element: HTMLElement, property: string,
value: string): void) and adjust other controllers to reuse it to avoid
duplication.
In `@src/ts/websites/x/x-controller.test.ts`:
- Around line 20-39: The test currently asserts only that the sidebar remains
hidden after controller.unfocus; add an assertion that the feed element returned
by XUtils.getXFeed (the feed variable) is restored to visible. After calling
controller.unfocus() in the test, add an assertion such as expecting
feed.style.display not to be 'none' (or to equal the expected visible value), so
the test name "while restoring the feed" matches the verification of XController
restoring the feed's visibility.
- Around line 24-27: The test mutates document.URL via Object.defineProperty in
x-controller.test.ts and doesn't restore it; update the test to capture the
original value before modifying (cache original = document.URL), apply
Object.defineProperty(document, 'URL', { value: 'https://x.com/home', writable:
true }) in the test setup, and add/modify an afterEach hook to restore
document.URL by re-applying Object.defineProperty(document, 'URL', { value:
original, writable: true }) (use the existing afterEach or add one if missing)
so the mutation does not leak between tests.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8518e6f3-f4f4-4974-ad66-8255d6701ac4
📒 Files selected for processing (26)
README.mdbuild.jsreports/selector-health/selector-health-report.jsonsrc/manifest.jsonsrc/options/options.csssrc/options/options.htmlsrc/options/options.html.d.tssrc/options/options.test.tssrc/options/options.tssrc/popup/popup.csssrc/popup/popup.htmlsrc/popup/popup.test.tssrc/popup/popup.tssrc/ts/__mocks__/webextension-polyfill-ts.tssrc/ts/background.tssrc/ts/focus/focus.tssrc/ts/focus/types.tssrc/ts/quotes/index.tssrc/ts/quotes/quotes.test.tssrc/ts/quotes/text-size.tssrc/ts/websites/linkedin/linkedin-controller.test.tssrc/ts/websites/linkedin/linkedin-controller.tssrc/ts/websites/website-config.tssrc/ts/websites/website-controller.tssrc/ts/websites/x/x-controller.test.tssrc/ts/websites/x/x-controller.ts
💤 Files with no reviewable changes (2)
- src/ts/websites/website-config.ts
- src/ts/quotes/text-size.ts
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
README.md (2)
9-12: Clarify YouTube behavior across both modes.LinkedIn and X explain what changes in Focused vs Unfocused, but YouTube only describes one state. In the new two-state UX, that leaves it unclear whether Unfocused behaves differently on YouTube or is intentionally identical.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 9 - 12, Update the "YouTube" bullet in the "Supported sites" section of README.md to describe both Focused and Unfocused modes explicitly: state what Focused mode hides (recommended videos, comments, suggestions, etc.) and what Unfocused mode restores (e.g., brings back recommendations and comments while still preserving video playback) or note if Unfocused is identical to Focused; ensure the bullet matches the style of the LinkedIn and X entries so readers can clearly see the differences between Focused and Unfocused for YouTube.
23-26: Document how users open the Settings page.This reads like a future placeholder, but the PR adds an options page entry point now. Please either add a concrete access path for users or explicitly say the page is scaffold-only today so the README does not undersell or obscure the new entry point.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 23 - 26, Update the "Settings page" README section to either (a) document the concrete access path for users (e.g., explain how to open the Options/Settings page via the extension menu or a specific URL like chrome-extension://<extension-id>/options.html and mention any keyboard/toolbar steps), or (b) explicitly state that the page is scaffold-only today and not yet user-facing; modify the "Settings page" header content accordingly so readers know whether the options page is reachable now or is a future placeholder.src/ts/websites/x/x-controller.test.ts (1)
31-57: Consider adding a focus-mode test case for completeness.The test verifies unfocus behavior, but there's no corresponding test for focus mode (verifying both feed and sidebar are hidden). This would provide better coverage of the two-state behavior mentioned in the PR objectives.
Example additional test
it('hides both feed and sidebar in focused mode', () => { const feed = document.createElement('div') const sidebar = document.createElement('aside') Object.defineProperty(document, 'URL', { value: 'https://x.com/home', writable: true, }) ;(XUtils.isHomePage as jest.Mock).mockReturnValue(true) ;(XUtils.getXFeed as jest.Mock).mockReturnValue(feed) ;(XUtils.getXSidebar as jest.Mock).mockReturnValue(sidebar) ;(XUtils.hasFeedLoaded as jest.Mock).mockReturnValue(true) ;(XUtils.isFeedHidden as jest.Mock).mockReturnValue(false) ;(XUtils.isSidebarHidden as jest.Mock).mockReturnValue(false) const controller = new XController() controller.focus() expect(feed.style.display).toBe('none') expect(sidebar.style.display).toBe('none') })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ts/websites/x/x-controller.test.ts` around lines 31 - 57, Add a complementary focused-mode unit test to x-controller.test.ts that mirrors the existing unfocused test: mock document.URL to a home URL and set XUtils.isHomePage, XUtils.getXFeed, XUtils.getXSidebar, XUtils.hasFeedLoaded, XUtils.isFeedHidden and XUtils.isSidebarHidden appropriately, instantiate XController and call controller.focus(), then assert that both the feed and sidebar elements have style.display === 'none' (use XController and its focus() method and the XUtils mocks to locate the behavior to test).src/ts/background.ts (1)
25-29: Consider handling the storage initialization promise.The
browser.storage.local.set()call at module scope doesn't handle potential rejection. While MV3 workers commonly use this pattern, a silent failure during startup could leave the extension in an inconsistent state without any indication.♻️ Optional: Add error handling for visibility
// In MV3 the worker restarts frequently. Reset appState on startup so each // worker begins from the same focused baseline. browser.storage.local.set({ appState: appState, -}) +}).catch((error) => { + console.error('Failed to initialize appState:', error); +});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ts/background.ts` around lines 25 - 29, The call to browser.storage.local.set({ appState }) at module scope ignores the returned promise and can silently fail; update the startup flow (e.g., in an async init function or an IIFE) to await or attach a .catch to browser.storage.local.set({ appState }) and surface failures via logging (console.error or your extension logger) and optional fallback behavior so errors during the appState initialization are visible and handled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/popup/popup.css`:
- Around line 53-59: The .title CSS rule uses a very tight line-height (0.92)
which risks clipping descenders when the title wraps due to the max-width:
250px; update the .title rule to increase line-height to at least 1.0–1.1 (or
remove the explicit line-height to use the browser default) and/or ensure the
title is constrained to a single line (e.g., disable wrapping) so descenders (g,
y, p, q, j) do not overlap; adjust the .title selector accordingly.
- Around line 87-92: The --accent color (`#3b82f6`) does not meet WCAG AA contrast
(4.5:1) against --paper (`#f5f7fa`), causing .index-label and .info-label to be
insufficiently readable; update the --accent CSS variable to a darker hue that
yields at least 4.5:1 contrast against --paper (or alternatively increase
font-weight/size for .index-label and .info-label), then verify contrast with a
tool. Locate the --accent definition (CSS custom property) and change it to a
darker hex that passes the 4.5:1 check, or adjust .index-label and .info-label
styles to ensure their rendered text meets the required contrast threshold.
Ensure you test the final color against --paper to confirm compliance.
In `@src/ts/websites/x/x-controller.test.ts`:
- Line 55: The test is asserting the wrong element — change the assertion to
verify the feed element that the controller manipulates: obtain the element from
getFeedElement() (which wraps XUtils.getXFeed()) or the mocked feed variable and
assert on its style.display after calling setFeedVisibility(), instead of
asserting on feedItem.style.display; update the test to call
XController.getFeedElement() or reference the mocked feed and
expect(feed.style.display).not.toBe('none') (and adjust any related setup to
ensure the feed is the mocked element).
---
Nitpick comments:
In `@README.md`:
- Around line 9-12: Update the "YouTube" bullet in the "Supported sites" section
of README.md to describe both Focused and Unfocused modes explicitly: state what
Focused mode hides (recommended videos, comments, suggestions, etc.) and what
Unfocused mode restores (e.g., brings back recommendations and comments while
still preserving video playback) or note if Unfocused is identical to Focused;
ensure the bullet matches the style of the LinkedIn and X entries so readers can
clearly see the differences between Focused and Unfocused for YouTube.
- Around line 23-26: Update the "Settings page" README section to either (a)
document the concrete access path for users (e.g., explain how to open the
Options/Settings page via the extension menu or a specific URL like
chrome-extension://<extension-id>/options.html and mention any keyboard/toolbar
steps), or (b) explicitly state that the page is scaffold-only today and not yet
user-facing; modify the "Settings page" header content accordingly so readers
know whether the options page is reachable now or is a future placeholder.
In `@src/ts/background.ts`:
- Around line 25-29: The call to browser.storage.local.set({ appState }) at
module scope ignores the returned promise and can silently fail; update the
startup flow (e.g., in an async init function or an IIFE) to await or attach a
.catch to browser.storage.local.set({ appState }) and surface failures via
logging (console.error or your extension logger) and optional fallback behavior
so errors during the appState initialization are visible and handled.
In `@src/ts/websites/x/x-controller.test.ts`:
- Around line 31-57: Add a complementary focused-mode unit test to
x-controller.test.ts that mirrors the existing unfocused test: mock document.URL
to a home URL and set XUtils.isHomePage, XUtils.getXFeed, XUtils.getXSidebar,
XUtils.hasFeedLoaded, XUtils.isFeedHidden and XUtils.isSidebarHidden
appropriately, instantiate XController and call controller.focus(), then assert
that both the feed and sidebar elements have style.display === 'none' (use
XController and its focus() method and the XUtils mocks to locate the behavior
to test).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ef8f0d28-d0ea-4c2d-89d9-3c068a218755
📒 Files selected for processing (12)
README.mdreports/selector-health/selector-health-report.jsonsrc/options/options.htmlsrc/options/options.tssrc/popup/popup.csssrc/popup/popup.tssrc/ts/background.tssrc/ts/quotes/index.tssrc/ts/quotes/quotes.test.tssrc/ts/websites/linkedin/linkedin-controller.test.tssrc/ts/websites/website-controller.tssrc/ts/websites/x/x-controller.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- src/ts/websites/website-controller.ts
- reports/selector-health/selector-health-report.json
- src/ts/websites/linkedin/linkedin-controller.test.ts
- src/options/options.ts
- src/options/options.html
| .title { | ||
| font-size: 38px; | ||
| line-height: 0.92; | ||
| letter-spacing: -0.06em; | ||
| max-width: 250px; | ||
| margin-top: 6px; | ||
| } |
There was a problem hiding this comment.
Tight line-height may cause text clipping on multi-line titles.
line-height: 0.92 combined with max-width: 250px could cause descenders (g, y, p, q, j) to overlap with the next line if the title wraps. If the title content is guaranteed to be single-line, this is fine; otherwise consider increasing to at least 1.0 or 1.1.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/popup/popup.css` around lines 53 - 59, The .title CSS rule uses a very
tight line-height (0.92) which risks clipping descenders when the title wraps
due to the max-width: 250px; update the .title rule to increase line-height to
at least 1.0–1.1 (or remove the explicit line-height to use the browser default)
and/or ensure the title is constrained to a single line (e.g., disable wrapping)
so descenders (g, y, p, q, j) do not overlap; adjust the .title selector
accordingly.
| .summary { | ||
| max-width: 240px; | ||
| font-size: 15px; | ||
| line-height: 1.4; | ||
| color: var(--muted); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Calculate contrast ratios for the color combinations
python3 << 'EOF'
def hex_to_rgb(hex_color):
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
def relative_luminance(rgb):
def channel(c):
c = c / 255.0
return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
r, g, b = rgb
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b)
def contrast_ratio(hex1, hex2):
l1 = relative_luminance(hex_to_rgb(hex1))
l2 = relative_luminance(hex_to_rgb(hex2))
lighter = max(l1, l2)
darker = min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
paper = "#f5f7fa"
ink = "#0f172a"
muted = "#5b6474"
accent = "#3b82f6"
print("WCAG AA requires 4.5:1 for normal text, 3:1 for large text (18px+ or 14px+ bold)")
print()
print(f"--ink on --paper: {contrast_ratio(ink, paper):.2f}:1")
print(f"--muted on --paper: {contrast_ratio(muted, paper):.2f}:1")
print(f"--accent on --paper: {contrast_ratio(accent, paper):.2f}:1")
EOFRepository: grey-software/focused-browsing
Length of output: 236
Fix --accent color contrast to meet WCAG AA standards.
The .summary text using --muted (#5b6474) on --paper (#f5f7fa) achieves 5.56:1 contrast, which exceeds WCAG AA requirements. However, .index-label and .info-label using --accent (#3b82f6) on the same background only achieve 3.43:1 contrast, falling short of the required 4.5:1 for normal text at 15px font size. The --accent color needs adjustment to improve readability.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/popup/popup.css` around lines 87 - 92, The --accent color (`#3b82f6`) does
not meet WCAG AA contrast (4.5:1) against --paper (`#f5f7fa`), causing
.index-label and .info-label to be insufficiently readable; update the --accent
CSS variable to a darker hue that yields at least 4.5:1 contrast against --paper
(or alternatively increase font-weight/size for .index-label and .info-label),
then verify contrast with a tool. Locate the --accent definition (CSS custom
property) and change it to a darker hex that passes the 4.5:1 check, or adjust
.index-label and .info-label styles to ensure their rendered text meets the
required contrast threshold. Ensure you test the final color against --paper to
confirm compliance.
| controller.focus() | ||
| controller.unfocus() | ||
|
|
||
| expect(feedItem.style.display).not.toBe('none') |
There was a problem hiding this comment.
Assertion checks wrong element — should verify feed, not feedItem.
Looking at x-controller.ts, getFeedElement() returns the result of XUtils.getXFeed() (which is feed here), and setFeedVisibility() operates on that element. The test asserts on feedItem.style.display, but feedItem is never directly manipulated by the controller. This assertion passes trivially because feedItem's display is never set.
Proposed fix
- expect(feedItem.style.display).not.toBe('none')
+ expect(feed.style.display).not.toBe('none')📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| expect(feedItem.style.display).not.toBe('none') | |
| expect(feed.style.display).not.toBe('none') |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ts/websites/x/x-controller.test.ts` at line 55, The test is asserting the
wrong element — change the assertion to verify the feed element that the
controller manipulates: obtain the element from getFeedElement() (which wraps
XUtils.getXFeed()) or the mocked feed variable and assert on its style.display
after calling setFeedVisibility(), instead of asserting on
feedItem.style.display; update the test to call XController.getFeedElement() or
reference the mocked feed and expect(feed.style.display).not.toBe('none') (and
adjust any related setup to ensure the feed is the mocked element).
This simplifies the extension into a more opinionated two-state experience and removes most popup customization and storage plumbing. It replaces the popup with a minimal status surface, adds an MV3 options page entry point, and updates the build and test setup to bundle that page. It removes per-site toggles, custom focus mode, quote display and text-size settings, and the old website config code while keeping the storage-based focus state flow intact. It also makes LinkedIn and X keep side distractions hidden in unfocused mode by default, including the LinkedIn layout-preserving panel concealment fix and regression tests.
Summary by CodeRabbit
New Features
Changes
Tests