From fd00fc1c87cd8819ae1b578d74ccff545601ded9 Mon Sep 17 00:00:00 2001 From: cottongin Date: Wed, 26 Aug 2026 14:34:15 -0400 Subject: [PATCH] Harden spec after adversarial review - Use div.thing.link selector to exclude comment nodes - Fix shared module loading (manifest content_scripts array) - Add FOUC mitigation via CSS-only manifest injection - Switch to storage.local to avoid sync quota limits - Add MutationObserver for RES dark mode toggle detection - Use fallback chain for MutationObserver target container - Clarify comment count text handling (zero = "0 comments") - Simplify teardown to page reload on master toggle - Expand test checklist to cover new behaviors Co-authored-by: Cursor --- .../2026-08-26-reddit-badge-tweaks-design.md | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/docs/superpowers/specs/2026-08-26-reddit-badge-tweaks-design.md b/docs/superpowers/specs/2026-08-26-reddit-badge-tweaks-design.md index be9d0ed..4618a64 100644 --- a/docs/superpowers/specs/2026-08-26-reddit-badge-tweaks-design.md +++ b/docs/superpowers/specs/2026-08-26-reddit-badge-tweaks-design.md @@ -29,7 +29,7 @@ firefox-reddit-tweaks/ │ ├── tweaks/ │ │ └── badges.js # Subreddit + comment badge tweak │ └── styles/ -│ └── badges.css # Badge styling +│ └── badges.css # Badge styling (also injected via manifest as CSS content script) ├── popup/ │ ├── popup.html # Settings UI │ ├── popup.js # Settings logic @@ -38,35 +38,37 @@ firefox-reddit-tweaks/ │ ├── icon-48.png │ └── icon-96.png └── lib/ - └── settings.js # Shared settings read/write (browser.storage.sync) + └── settings.js # Shared settings read/write (browser.storage.local) ``` ### Manifest - Manifest V2 (Firefox fully supports V2). -- `content_scripts` matched to `*://old.reddit.com/*` and `*://www.reddit.com/*`. On `www.reddit.com`, the script naturally handles the old-vs-new ambiguity because it targets `div.thing` elements, which only exist when old Reddit is rendered. +- `content_scripts` matched to `*://old.reddit.com/*` and `*://www.reddit.com/*`. On `www.reddit.com`, the script naturally handles the old-vs-new ambiguity because it targets `div.thing.link` elements, which only exist when old Reddit is rendered. +- `content_scripts.css` includes `badges.css` for immediate injection at CSS parse time (prevents FOUC by hiding original elements before JS runs). +- `content_scripts.js` lists all JS files in order: `lib/settings.js`, `content/tweaks/badges.js`, `content/main.js`. Content scripts cannot use ES module `import`/`export`, so all files share the same global scope via the manifest's script array. - Permissions: `storage` (for user settings), `activeTab`. - `browser_action` for the popup. ### Modularity -Each tweak is an individual file under `content/tweaks/`, responsible for one self-contained modification. `main.js` loads settings and calls each enabled tweak. Adding a new tweak means: drop a file in `tweaks/`, wire it in `main.js`. +Each tweak is an individual file under `content/tweaks/`, responsible for one self-contained modification. `main.js` loads settings and calls each enabled tweak. Adding a new tweak means: drop a file in `tweaks/`, add it to the manifest's `content_scripts.js` array, and wire it in `main.js`. ## Badge Tweak — DOM Manipulation ### Source Elements -Old Reddit renders each post as a `div.thing`. Relevant children: +Old Reddit renders each post as a `div.thing.link` (comments use `div.thing.comment` — these are excluded). Relevant children of a post: - **Subreddit name:** `a.subreddit` inside `p.tagline` - **Comment count:** `a.comments` inside `ul.flat-list.buttons` ### Process -1. Query all `div.thing` elements on the page. -2. For each post, extract the subreddit link's `href`/text and the comments link's `href`/text (parsing the number from the text). +1. Query all `div.thing.link` elements on the page (the `.link` class distinguishes posts from comments, avoiding wasted iteration over comment nodes). +2. For each post, extract the subreddit link's `href`/text and the comments link's `href`/text. The comments badge uses the original text verbatim, except when the text is `"comment"` (meaning zero comments), in which case the badge displays `"0 comments"`. 3. Create two badge elements (styled `` tags) and insert them into a new container `div.reddit-tweaks-badges`, placed as the first child of the post's `div.entry` (above the title). -4. Hide the original subreddit element in the tagline and the comments link in the flat-list via CSS (`display: none`). +4. The original subreddit element in the tagline and the comments link in the flat-list are hidden via CSS rules in `badges.css` (injected at CSS parse time by the manifest, so they're hidden before JS runs). 5. Mark each processed post with `data-rt-processed` to prevent double-processing. ### Badge HTML Structure @@ -82,7 +84,7 @@ All injected elements use an `rt-` prefix to avoid class name collisions with Re ### Dynamic Content -A `MutationObserver` on `div#siteTable` (the post container) watches for new `div.thing` children, covering RES infinite scroll and Reddit's own pagination. New posts are processed on insertion. +A `MutationObserver` watches for new `div.thing.link` children being added to the page, covering RES infinite scroll and Reddit's own pagination. The observer target uses a fallback chain for robustness across page types: `#siteTable` (standard listings) → `.content[role="main"]` (fallback) → `document.body` (last resort). New posts are processed on insertion. ## Badge Styling @@ -113,7 +115,7 @@ A `MutationObserver` on `div#siteTable` (the post container) watches for new `di ### Dark Mode Detection -The content script checks for RES night mode via the `.res-nightmode` class on `` and applies the dark palette via a CSS class toggle (`rt-dark`). Users can also force a mode in settings (Auto / Light / Dark). +The content script checks for RES night mode via the `.res-nightmode` class on `` and applies the dark palette via a CSS class toggle (`rt-dark`). A `MutationObserver` on `` watches for `class` attribute changes to detect RES dark mode being toggled mid-session, updating badge colors reactively. Users can also force a mode in settings (Auto / Light / Dark), which bypasses detection entirely. ### CSS Custom Properties @@ -125,7 +127,7 @@ For per-subreddit overrides, inline styles are set on individual badges when a m ### Storage -All settings stored in `browser.storage.sync` for cross-device sync. +All settings stored in `browser.storage.local` (no quota concerns, avoids `storage.sync` item size limits for per-subreddit overrides). ### Configurable Properties @@ -149,7 +151,7 @@ All settings stored in `browser.storage.sync` for cross-device sync. ### Live Updates -The content script listens for `browser.storage.onChanged` to apply settings changes without requiring a page reload. +The content script listens for `browser.storage.onChanged` to apply color/dark-mode settings changes without requiring a page reload. For the master toggle (`enabled`), the page reloads to cleanly apply or remove all tweaks — this avoids complex teardown logic for a rare user action. ## Settings UI (Popup) @@ -170,8 +172,9 @@ Settings save immediately on change. Clean, minimal styling following Firefox ex - **Already processed:** `data-rt-processed` attribute prevents double-processing. - **RES infinite scroll:** MutationObserver catches dynamically added posts. - **Subreddit pages:** Subreddit badge is skipped when the subreddit name isn't present in the post tagline (it's implied by the page context). -- **Comment pages:** The main post at the top is a `div.thing` and gets badges. Comment entries lack subreddit/comment-count structure and are naturally skipped. -- **Extension disabled:** When `enabled` is `false`, the content script does nothing and any previously injected badges/styles are removed. +- **Comment pages:** The main post at the top is a `div.thing.link` and gets badges. Comment entries are `div.thing.comment` and are excluded by the selector. +- **Zero comments:** When the original comments link text is `"comment"` (no number), the badge displays `"0 comments"` with the same link destination. +- **Extension disabled:** When `enabled` is toggled to `false`, the page reloads. The content script checks `enabled` on startup and exits early if `false`, leaving the page in its default state. ## Testing Strategy @@ -183,9 +186,14 @@ Manual testing via `about:debugging` (temporary add-on). No automated test frame 2. Badges appear on subreddit listing pages (subreddit badge omitted when redundant) 3. Badges appear on user profile pages 4. Badges are clickable links to correct destinations -5. Original subreddit/comment elements are hidden +5. Original subreddit/comment elements are hidden immediately (no FOUC) 6. RES infinite scroll loads get badged 7. Dark mode detection works with RES night mode -8. Settings changes apply live without page reload -9. Per-subreddit color overrides work -10. Reset to defaults works +8. Toggling RES dark mode mid-session updates badge colors +9. Color settings changes apply live without page reload +10. Master toggle change triggers page reload and cleanly disables/enables +11. Per-subreddit color overrides work +12. Reset to defaults works +13. Posts with zero comments display "0 comments" badge +14. Comment nodes (`div.thing.comment`) are not processed +15. Badges work on search results and multi-reddit pages