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 <cursoragent@cursor.com>
This commit is contained in:
@@ -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 `<a>` 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 `<body>` 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 `<body>` and applies the dark palette via a CSS class toggle (`rt-dark`). A `MutationObserver` on `<body>` 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
|
||||
|
||||
Reference in New Issue
Block a user