Add design spec for inline subreddit color popover
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
219
docs/superpowers/specs/2026-08-26-inline-color-popover-design.md
Normal file
219
docs/superpowers/specs/2026-08-26-inline-color-popover-design.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Inline Subreddit Color Popover — Design Spec
|
||||
|
||||
**Date:** 2026-08-26
|
||||
**Status:** Draft
|
||||
**Parent spec:** `2026-08-26-reddit-badge-tweaks-design.md`
|
||||
|
||||
## Overview
|
||||
|
||||
Add an inline color-override popover to subreddit badges, letting users set per-subreddit colors directly on the page without opening the extension popup. A gear icon appears on hover over a subreddit badge; clicking it opens a floating popover with color pickers, a live preview, and Save/Cancel/Reset controls. The popover writes to the same `subredditColors` storage used by the popup, keeping both UIs in sync.
|
||||
|
||||
## Approach
|
||||
|
||||
Single shared popover. One `<div class="rt-color-popover">` element is injected into `document.body` once when the badge tweak initializes. Opening the popover for any subreddit re-positions and re-populates this single element. This avoids creating hidden popover elements per post and ensures only one popover is open at a time.
|
||||
|
||||
## Gear Icon — Trigger
|
||||
|
||||
Each subreddit badge (`<a class="rt-badge rt-badge--subreddit">`) gets a `<span class="rt-badge-gear">` appended as a child, containing a gear icon character (⚙). The gear is hidden by default and shown via CSS when the badge is hovered:
|
||||
|
||||
```css
|
||||
.rt-badge-gear {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
.rt-badge--subreddit:hover .rt-badge-gear {
|
||||
opacity: 0.6;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.rt-badge-gear:hover {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
```
|
||||
|
||||
Because the gear is a child of the badge `<a>`, hovering over the gear maintains the parent's `:hover` state — no gap or flicker when moving the cursor from the text to the gear.
|
||||
|
||||
**Gear icon properties:**
|
||||
- `display: inline` always, visibility controlled via `opacity` + `pointer-events` (allows CSS transition).
|
||||
- Font size: 10–11px, matching badge text proportion.
|
||||
- Color: inherits badge text color. At rest (parent hovered): `opacity: 0.6`. On direct gear hover: `opacity: 1`.
|
||||
- Left margin: `4px` to separate from subreddit text.
|
||||
- `cursor: pointer`.
|
||||
|
||||
**Click behavior:**
|
||||
- `event.preventDefault()` — prevents navigating the subreddit link.
|
||||
- `event.stopPropagation()` — prevents bubbling to the badge click handler.
|
||||
- Calls `openPopover()` with the badge element, subreddit name, and current settings.
|
||||
|
||||
Only subreddit badges get the gear icon. Comments badges do not.
|
||||
|
||||
## Popover — Structure
|
||||
|
||||
The popover is a `<div class="rt-color-popover">` appended to `document.body` with `position: absolute` and a high `z-index` (e.g., `10000`).
|
||||
|
||||
### Contents
|
||||
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
│ r/javascript [×] │ ← header with subreddit name and close button
|
||||
├──────────────────────────┤
|
||||
│ Background [■] #fde8e0 │ ← color swatch + hex text input
|
||||
│ Border [■] #c84a20 │
|
||||
│ Text [■] #c84a20 │
|
||||
├──────────────────────────┤
|
||||
│ Preview: [ r/javascript ]│ ← live preview badge
|
||||
├──────────────────────────┤
|
||||
│ [Reset Default] [Save]│ ← action buttons
|
||||
│ [Cancel]│
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
- **Header:** Subreddit name as a label (e.g., "r/javascript"), truncated with ellipsis if long. Close button (×) on the right.
|
||||
- **Color rows:** Three rows, one per property (background, border, text). Each row has a label, an `<input type="color">` swatch, and a `<input type="text">` for hex value. The two inputs are synced bidirectionally.
|
||||
- **Live preview:** A `<span>` styled as an `rt-badge rt-badge--subreddit`, with the subreddit name as text content. Inline styles update in real time as the user adjusts any color input.
|
||||
- **Footer:** "Reset to Default" button (left), "Save" and "Cancel" buttons (right).
|
||||
|
||||
### Positioning
|
||||
|
||||
On open, the popover is positioned below the clicked badge, aligned to its left edge:
|
||||
|
||||
1. Get the badge's `getBoundingClientRect()`.
|
||||
2. Calculate `top = rect.bottom + window.scrollY + 6px` (6px gap).
|
||||
3. Calculate `left = rect.left + window.scrollX`.
|
||||
4. If the popover would overflow the viewport bottom, flip above: `top = rect.top + window.scrollY - popoverHeight - 6px`.
|
||||
5. Clamp `left` so the popover doesn't overflow left or right edges.
|
||||
|
||||
A small CSS arrow (`::before` pseudo-element) points toward the badge.
|
||||
|
||||
### Lifecycle
|
||||
|
||||
- **Open:** Clicking a gear icon calls `openPopover()`. If the popover is already open for a different subreddit, it re-positions and re-populates (no stacking). If the popover is already open for the *same* subreddit, the click toggles it closed (cancel behavior).
|
||||
- **Close without saving:** Clicking Cancel, clicking the × button, clicking outside the popover, pressing Escape, or toggling the same gear icon.
|
||||
- **Close with saving:** Clicking Save.
|
||||
- **Dark mode toggle while open:** If dark mode changes while the popover is open (e.g., user toggles RES night mode), the popover's visual theme updates via the existing dark mode observer, but the color *values* in the inputs are not changed — they reflect the user's in-progress edits regardless of mode.
|
||||
|
||||
## Color Editing and Preview
|
||||
|
||||
### Initial values
|
||||
|
||||
When the popover opens for a subreddit:
|
||||
- If an override exists in `settings.subredditColors[subredditName]`, those values populate the inputs.
|
||||
- If no override exists, the inputs show the current global subreddit badge colors for the active mode (light or dark), derived from `settings[prefix + ".subredditBgColor"]` etc. This way the user starts from the "current appearance" and can adjust from there.
|
||||
|
||||
### Input syncing
|
||||
|
||||
- Changing the `<input type="color">` updates the text input's value.
|
||||
- Changing the text input updates the color picker if the value is a valid hex color (`/^#[0-9a-f]{6}$/i`). Invalid values show a subtle red border on the text input but do not update the picker.
|
||||
|
||||
### Live preview
|
||||
|
||||
- As any color input changes (via `input` event), the preview badge's inline styles update immediately.
|
||||
- The preview badge is styled identically to a real subreddit badge (same classes), with inline `style` overrides for the three color properties.
|
||||
- Actual page badges are **not** updated until Save is clicked.
|
||||
|
||||
## Save, Cancel, and Reset
|
||||
|
||||
### Save
|
||||
|
||||
1. Writes `{bg, border, text}` into `settings.subredditColors[subredditName]`.
|
||||
2. Calls `RedditTweaks.saveSettings(settings)` → writes to `browser.storage.local`.
|
||||
3. The existing `storage.onChanged` listener in `main.js` fires, calling `updateColors()`, which re-applies per-subreddit overrides to all badges on the page — all instances of that subreddit update, not just the one that was clicked.
|
||||
4. Closes the popover.
|
||||
|
||||
### Cancel
|
||||
|
||||
- Dismisses the popover without writing anything.
|
||||
- Triggered by: clicking Cancel, clicking ×, clicking outside the popover, pressing Escape.
|
||||
- Page badges remain unchanged.
|
||||
|
||||
### Reset to Default
|
||||
|
||||
1. Deletes `settings.subredditColors[subredditName]` (removes the key entirely).
|
||||
2. Saves the updated settings.
|
||||
3. The badge reverts to global colors via the `updateColors()` flow.
|
||||
4. The popover inputs update to show the global defaults so the user sees what "default" means.
|
||||
5. The popover **stays open** so the user can confirm or adjust further.
|
||||
|
||||
### Sync with popup
|
||||
|
||||
Both the inline popover and the popup's per-subreddit overrides section read/write the same `subredditColors` object in `browser.storage.local`. Changes made inline appear in the popup next time it's opened, and vice versa. No additional sync logic is needed.
|
||||
|
||||
## Styling
|
||||
|
||||
### Popover
|
||||
|
||||
- White background (`#fff`), `1px solid #ccc` border, `border-radius: 6px`.
|
||||
- `box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15)` for depth.
|
||||
- Padding: `12px`.
|
||||
- Max-width: `260px`.
|
||||
- Font: same stack as the extension (`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`), 13px base.
|
||||
- CSS arrow (via `::before` pseudo-element) pointing toward the anchor badge.
|
||||
|
||||
### Dark mode variant
|
||||
|
||||
The popover detects the current mode using the same `isDarkMode(settings)` logic in `badges.js`. When dark:
|
||||
- Background: `#2a2a2a`.
|
||||
- Border: `#555`.
|
||||
- Text: `#e0e0e0`.
|
||||
- Input borders adjusted for contrast.
|
||||
- Applied via `.rt-dark` class on the popover element, consistent with the existing badge dark mode approach.
|
||||
|
||||
### Color input rows
|
||||
|
||||
- Flex layout: label (left), color swatch + hex text (right).
|
||||
- Color swatches: `28px × 20px`, `1px solid #ccc` border, `border-radius: 3px`.
|
||||
- Hex text inputs: `70px` wide, monospace font, `font-size: 12px`.
|
||||
|
||||
### Buttons
|
||||
|
||||
- **Save:** Accent blue background (`#4a90d9`), white text, `border-radius: 3px`.
|
||||
- **Cancel:** Text-only or light secondary style, same as popup's `.btn-secondary`.
|
||||
- **Reset to Default:** Secondary style, positioned on the left side of the footer to avoid accidental clicks near Save.
|
||||
|
||||
### All classes use the `rt-` prefix
|
||||
|
||||
Consistent with the rest of the extension: `rt-color-popover`, `rt-popover-header`, `rt-popover-row`, `rt-popover-preview`, `rt-popover-footer`, etc.
|
||||
|
||||
## File Changes
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `content/tweaks/badges.js` | Add gear icon injection in `processPost()`. Add popover creation, open/close/save/reset functions. |
|
||||
| `content/styles/badges.css` | Add styles for `.rt-badge-gear`, `.rt-color-popover` and its children, light and dark variants. |
|
||||
| `lib/settings.js` | No changes. |
|
||||
| `content/main.js` | No changes. |
|
||||
| `popup/popup.js` | No changes. |
|
||||
| `popup/popup.html` | No changes. |
|
||||
| `manifest.json` | No changes. |
|
||||
|
||||
Two files modified, zero new files.
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- **Subreddit badge absent:** No gear icon added, no popover trigger. Follows existing behavior where the subreddit badge is skipped.
|
||||
- **Popover open when page scrolls:** Popover uses `position: absolute` and scrolls with page content. No repositioning needed.
|
||||
- **Popover open when RES loads new posts:** New posts get processed normally. The open popover stays in place.
|
||||
- **Multiple rapid gear clicks:** Single popover instance re-positions and re-populates. No stacking.
|
||||
- **Invalid hex input:** Text input shows subtle error border. Save uses the color picker's last valid value.
|
||||
- **Very long subreddit names:** Header truncates with `text-overflow: ellipsis`.
|
||||
- **Extension disabled mid-session:** Page reloads (existing behavior), removing all injected elements including the popover.
|
||||
|
||||
## Testing
|
||||
|
||||
Manual testing additions:
|
||||
|
||||
1. Hovering over a subreddit badge shows the gear icon; moving off hides it.
|
||||
2. Moving from badge text to gear icon does not flicker/hide the gear.
|
||||
3. Clicking the gear opens the popover positioned near the badge.
|
||||
4. Clicking the gear does not navigate the subreddit link.
|
||||
5. Popover shows current override values (or global defaults if no override exists).
|
||||
6. Changing colors updates the live preview badge in real time.
|
||||
7. Actual page badges do not change until Save is clicked.
|
||||
8. Clicking Save persists the override and updates all badges for that subreddit on the page.
|
||||
9. Clicking Cancel (or outside, or Escape) dismisses without saving.
|
||||
10. Reset to Default removes the override, reverts badge to global colors, and shows global values in the popover.
|
||||
11. Inline changes appear in the popup's per-subreddit overrides section.
|
||||
12. Popup changes to per-subreddit overrides are reflected when the inline popover is next opened.
|
||||
13. Gear icon does not appear on comments badges.
|
||||
14. Popover dark mode variant works when RES night mode is active.
|
||||
15. Only one popover open at a time — clicking a different gear re-positions the popover.
|
||||
Reference in New Issue
Block a user