Skip to content

fix(ui): popup signal reduction + direct toolbar icon (v1.6.1)#37

Merged
helebest merged 1 commit into
mainfrom
fix/popup-signal-reduction-and-direct-icon
Apr 22, 2026
Merged

fix(ui): popup signal reduction + direct toolbar icon (v1.6.1)#37
helebest merged 1 commit into
mainfrom
fix/popup-signal-reduction-and-direct-icon

Conversation

@helebest

Copy link
Copy Markdown
Owner

Summary

One release addressing all open UX feedback from @sergeevabc (#36 new, #28 tail):

  • Popup signal reduction — removed .status-dot (top chip) and .action-check (card right-edge ✓). Active-mode now uses blue-gradient + white-inherited card icon only. 4 signals → 2.
  • Empty-state CTA deduplication — the header + button is hidden while no profiles exist, so the big "Add your first profile" CTA is the single entry point. Closes Repetitive elements to create a new profile #36.
  • Direct vs System toolbar icon — added a green arrow icon-direct-{16,32,48,128}.png family so Direct mode looks different from System (inactive gray) in the Chrome toolbar. Addresses Where is direct mode #28 tail.
  • Consolidation — extracted resolveIconPaths() + PROFILE_COLORS/ICON_SIZES into a new lib/icon-paths.js, single source of truth shared by background worker, icon generator script, and options page.
  • EfficiencyupdateIcon() short-circuits on unchanged state so chrome.action.setIcon IPC isn't called repeatedly on tab events; duplicate isHostProxied() evaluation hoisted.
  • Cleanup — dropped dead .active/.inactive/.proxy/.direct classList churn in updateStatusIndicator() (no CSS rules remained after .status-dot deletion).

Closes #36. Addresses sergeevabc's tail comment on closed #28.

Changes

  • popup.html — removed .status-dot + two .action-check blocks
  • popup.css — removed dot/check rules + pulse @keyframes, reflowed .action-card grid, added body.state-empty #addProfileBtn { display: none }
  • popup.jsupdateProfilesList() toggles body.state-empty; updateStatusIndicator() simplified to text-only
  • background.jsupdateIcon(color, mode) with no-op guard; all call sites updated to pass mode explicitly; lastIconMode / lastIconPaths exposed via GET_STATE for tests
  • lib/icon-paths.js (new) — resolveIconPaths(), PROFILE_COLORS, COLOR_NAMES, ICON_SIZES
  • scripts/generate-icons.js — imports shared constants; registers new proxy-icon-direct.svg
  • options.js — imports PROFILE_COLORS to build validColors
  • public/icons/proxy-icon-direct.svg + 4 generated PNGs
  • manifest.json / package.json — 1.6.0 → 1.6.1
  • CHANGELOG.md — new [1.6.1] - 2026-04-22 section

Regression guards

  • tests/update-icon.test.js — 8 Vitest assertions on the path resolver's three-way branch
  • e2e/popup-visual-simplicity.spec.ts.status-dot / .action-check removed from DOM, #addProfileBtn toggles with state-empty
  • e2e/icon-differentiation.spec.ts — asserts Direct and System resolve to strictly different setIcon paths via GET_STATE contract extension
  • e2e/popup-visual.spec.ts — 5 baseline screenshots (empty / has-profiles / direct-active / system-active / profile-active) locked under maxDiffPixelRatio: 0.02

Test plan

  • npm run type-check — clean
  • npm test — 91 unit tests pass
  • npm run test:e2e — 82 passed / 1 skipped / 0 failed (full suite, including new popup-visual + icon-differentiation specs)
  • npm run build — dist/ rebuilt with v1.6.1 manifest
  • npm run generate-icons — all 48 icons including new icon-direct-* produced
  • Manual smoke in Chrome: empty-state CTA → options open → create profile → popup shows +, no empty CTA → Direct card blue/no-checkmark → System card blue/no-checkmark → toolbar icon switches green arrow ↔ gray inactive on Direct ↔ System
  • Merge and let CI run on Node 20/22

Credits

Thanks @sergeevabc for the detailed UX feedback that drove this release.

…r icon (v1.6.1)

- Remove `.status-dot` pulsing dot and `.action-check` right-edge ✓ on action
  cards. Active mode now signaled via blue gradient + white-inherited icon
  only (4 signals → 2). Closes #36 visual half, addresses #28 tail.
- Hide header "+" button when profile list is empty so the big CTA is the
  single "add profile" entry point in empty state. Closes #36.
- Add icon-direct-{16,32,48,128}.png (green arrow) so Direct mode is visually
  distinct from System mode in the toolbar. Addresses #28 tail.
- Extract resolveIconPaths() + shared PROFILE_COLORS/ICON_SIZES into
  lib/icon-paths.js; consolidate copies from background.js, generate-icons.js,
  options.js into one source of truth.
- updateIcon() now gains a short-circuit guard against no-op setIcon IPC
  calls on recurring tab events, and hoists duplicate isHostProxied() calls.
- Drop dead .active/.inactive/.proxy/.direct classList churn in
  updateStatusIndicator() — those classes had no CSS rules after .status-dot
  was removed.

Regression guards: tests/update-icon.test.js (8 assertions),
e2e/popup-visual-simplicity.spec.ts, e2e/icon-differentiation.spec.ts,
e2e/popup-visual.spec.ts with 5 baseline screenshots.

Bumps manifest.json and package.json to 1.6.1.

Copy link
Copy Markdown
Owner Author

Code Review — PR #37: fix(ui): popup signal reduction + direct toolbar icon (v1.6.1)

Summary

Thorough review completed. This is a well-structured UX polish release that addresses feedback from #28 (tail) and #36. The PR description is comprehensive and accurately reflects every code change. Approved.


1. PR Description ↔ Implementation Consistency ✅

Every item listed in the PR summary has a corresponding code change:

Claim Verified
.status-dot removed ✅ HTML span gone, all CSS rules (.status-dot, @keyframes pulse, .status-indicator.active/inactive/proxy/direct .status-dot) deleted
.action-check removed ✅ Both SVG blocks removed from popup.html, CSS rules (.action-check, .action-card.selected .action-check) deleted, grid changed from auto 1fr autoauto 1fr
Empty-state CTA deduplication body.state-empty #addProfileBtn { display: none } added in CSS, document.body.classList.toggle('state-empty', ...) in popup.js
lib/icon-paths.js consolidation PROFILE_COLORS, COLOR_NAMES, ICON_SIZES, resolveIconPaths() extracted; imported by background.js, options.js, generate-icons.js
updateIcon() short-circuit optimization ✅ Early return when profileColor === lastIconColor && mode === lastIconMode
Direct icon family proxy-icon-direct.svg + 4 PNGs added; generate-icons.js registers the new entry
Version bump manifest.json + package.json: 1.6.0 → 1.6.1
CHANGELOG ✅ New [1.6.1] - 2026-04-22 section with accurate descriptions

2. Multi-Dimensional Analysis

Correctness

  • resolveIconPaths() three-way branching (profile color → direct → inactive) is logically sound.
  • Profile color takes precedence over mode — the right defensive choice, because showing a "bypass" icon while a proxy is active would mislead worse than the reverse.
  • Unknown/unrecognized hex colors gracefully fall back to icon-inactive rather than generating broken 404 paths.
  • All 12 updateIcon() call sites in background.js consistently pass (color, mode) — no stale single-arg calls remain.

Code Quality / Design

  • Excellent DRY refactoring: the three-way duplication of PROFILE_COLORS (previously in background.js, generate-icons.js, and options.js) is now a single source of truth in lib/icon-paths.js.
  • resolveIconPaths() is a pure function with no side effects — ideal for unit testing.
  • The updateIcon() short-circuit optimization is well-motivated: onActivated/onUpdated/onFocusChanged fire constantly and chrome.action.setIcon is real IPC.
  • updateStatusIndicator() cleanup removes dead classList churn that had no matching CSS rules after the dot deletion.

Security

  • No user input handling changes, no external API calls, no dynamic code generation.
  • The SVG is clean — no embedded scripts or external references.
  • No OWASP concerns.

Effectiveness

  • Directly addresses the user feedback: 4 signals → 2, Direct vs System visually distinct in toolbar.
  • The consolidation into lib/icon-paths.js reduces future maintenance burden and eliminates "keep in sync" comments.

3. Test Coverage ✅

Test File Type Coverage
tests/update-icon.test.js Unit (Vitest) 7 cases covering all 3 branches + edge cases (unknown color, unknown mode, color-wins-over-mode precedence, direct-vs-system distinctness)
e2e/popup-visual-simplicity.spec.ts E2E DOM assertions: .status-dot count=0, .action-check count=0, #addProfileBtn hidden/shown toggle, body.state-empty class
e2e/icon-differentiation.spec.ts E2E Direct→icon-direct-*, System→icon-inactive-*, strict path inequality, round-trip repaint
e2e/popup-visual.spec.ts E2E 5 screenshot baselines locked at maxDiffPixelRatio: 0.02

Edge cases are well covered: unrecognized hex, unknown mode, inconsistent (color, 'direct') pair.

4. Regression Verification ✅

All CI-equivalent checks pass locally:

✅ npm test          — 91 unit tests passed (6 test files)
✅ npm run build     — all 3 targets (background, popup, options) built successfully
✅ npm run type-check — clean, no TypeScript errors
✅ npm run generate-icons — all 48 icons generated (including new icon-direct-* family)

5. Minor Observation (non-blocking)

In updateIconForActiveTab(), when a profile with routing rules is active but the current tab is a non-HTTP page (e.g., chrome://newtab), the icon mode is passed as 'system'. This is semantically reasonable (no proxy traffic flows on that tab), though one could argue a distinct state like 'inactive' might be more precise. The current behavior is fine — it shows gray, which correctly communicates "not proxied on this tab."


Verdict: Approved. Clean implementation, thorough test coverage, no regressions. Ready to merge. 🟢


Generated by Claude Code

@helebest helebest merged commit 8676651 into main Apr 22, 2026
10 checks passed
helebest added a commit that referenced this pull request Apr 22, 2026
* fix(ui): reduce popup signal redundancy + differentiate direct toolbar icon (v1.6.1)

- Remove `.status-dot` pulsing dot and `.action-check` right-edge ✓ on action
  cards. Active mode now signaled via blue gradient + white-inherited icon
  only (4 signals → 2). Closes #36 visual half, addresses #28 tail.
- Hide header "+" button when profile list is empty so the big CTA is the
  single "add profile" entry point in empty state. Closes #36.
- Add icon-direct-{16,32,48,128}.png (green arrow) so Direct mode is visually
  distinct from System mode in the toolbar. Addresses #28 tail.
- Extract resolveIconPaths() + shared PROFILE_COLORS/ICON_SIZES into
  lib/icon-paths.js; consolidate copies from background.js, generate-icons.js,
  options.js into one source of truth.
- updateIcon() now gains a short-circuit guard against no-op setIcon IPC
  calls on recurring tab events, and hoists duplicate isHostProxied() calls.
- Drop dead .active/.inactive/.proxy/.direct classList churn in
  updateStatusIndicator() — those classes had no CSS rules after .status-dot
  was removed.

Regression guards: tests/update-icon.test.js (8 assertions),
e2e/popup-visual-simplicity.spec.ts, e2e/icon-differentiation.spec.ts,
e2e/popup-visual.spec.ts with 5 baseline screenshots.

Bumps manifest.json and package.json to 1.6.1.

* chore: update package-lock.json after npm install

Generated during PR #37 review regression testing.

https://claude.ai/code/session_01F7LmZmhQk4Mm2wv4JAxPxk

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Repetitive elements to create a new profile Where is direct mode

1 participant