Skip to content

Move magnifier to external package, hue-hunter#2509

Merged
RobbieTheWagner merged 4 commits intomainfrom
hue-hunter
Jan 22, 2026
Merged

Move magnifier to external package, hue-hunter#2509
RobbieTheWagner merged 4 commits intomainfrom
hue-hunter

Conversation

@RobbieTheWagner
Copy link
Copy Markdown
Member

@RobbieTheWagner RobbieTheWagner commented Jan 21, 2026

Summary by CodeRabbit

  • New Features

    • Switched to the hue-hunter color picker; Wayland color picking now uses PipeWire + XDG Portal.
  • Documentation

    • Updated README and docs to reflect the new color picker and revised prerequisites and build/packaging guidance.
  • Chores

    • Removed legacy magnifier and Rust sampler code, tests, configs, and related assets.
    • Simplified build/test scripts and packaging; added hue-hunter dependency.
    • Updated CI workflows: removed separate Rust test workflow, bumped checkout action, and added system dependency installation steps.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

This PR removes the Rust-based magnifier/pixel-sampler subsystem and associated magnifier UI, tests, and build wiring, and replaces the in-app picker launcher with a hue-hunter-based color picker integration. (50 words)

Changes

Cohort / File(s) Summary
Rust sampler crate
electron-app/magnifier/rust-sampler/*
Entire Rust crate removed: Cargo.toml, src (main, types, samplers for linux/macos/windows/wayland), tests, docs, WAYLAND status, and .gitignore — removes native sampling code and JSON IPC protocol.
Magnifier UI & renderer
electron-app/magnifier/*
Deleted magnifier UI and renderer assets: HTML, CSS, renderer/main/preload/main-rust, pixel-grid utils, grid-calculation, types, Vite config, and related TS modules.
Magnifier tests
electron-app/magnifier/**/*.test.ts, electron-app/magnifier/rust-sampler/tests/*
Removed comprehensive TypeScript and Rust unit/integration tests for magnifier logic, grid utils, Rust sampler manager, and platform samplers.
Rust sampler manager (TS)
electron-app/magnifier/rust-sampler-manager.ts, electron-app/magnifier/rust-sampler-manager.test.ts
Removed RustSamplerManager TypeScript class and its tests that spawned/managed the native sampler subprocess.
Color picker integration
electron-app/src/color-picker.ts
Rewritten to use hue-hunter + color-name-list/nearest-color; adds/exported launchPicker(mb, type?) and IPC flows to send picked color to renderer.
Build, packaging & CI
.github/workflows/*, forge.config.ts, package.json, vite.magnifier.config.ts, electron-app/tsconfig.json, eslint.config.mjs, .prettierignore
Removed Rust build scripts & workflows, updated CI to install Linux deps and bumped checkout action; replaced Rust extraResource with hue-hunter binary; removed magnifier from TS include; adjusted ignore entries.
Global types & docs
types/global.d.ts, README.md, WARP.md
Removed window.magnifierAPI global type; updated README and WARP to document hue-hunter integration and remove Rust magnifier references.

Sequence Diagram(s)

sequenceDiagram
  participant MB as Menubar (main)
  participant Picker as hue-hunter ColorPicker
  participant IPC as Electron IPC / Renderer

  MB->>MB: hide menubar window
  MB->>Picker: launch color picker
  Picker-->>MB: returns selected color or cancel
  MB->>IPC: send picked color via IPC (changeColor / pickContrast*)
  MB->>MB: show menubar window
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

breaking

Poem

"I hopped through code with nimble feet,
Rust crumbs swept clean beneath my seat,
Hue-hunter hums a brighter tune,
I twitch my nose — a perfect rune,
🐇🌈"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: replacing the internal magnifier implementation with an external hue-hunter package.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

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)

27-49: Align Wayland requirements with the new “no external tools” note.

The install block still recommends grim, which conflicts with the new Wayland note saying no external tools are needed. Consider removing those lines to avoid confusion.

📝 Suggested doc tweak
 # Debian / Ubuntu
 sudo apt-get install libxcb1 libxrandr2 libdbus-1-3
 
-# For Wayland (recommended)
-sudo apt-get install grim
-
 # For X11 (fallback)
 sudo apt-get install imagemagick xdotool
 # OR
 sudo apt-get install scrot xdotool
@@
 # Alpine
-sudo apk add libxcb libxrandr dbus grim
+sudo apk add libxcb libxrandr dbus
🤖 Fix all issues with AI agents
In `@electron-app/src/color-picker.ts`:
- Around line 14-18: The colorNameFn currently assumes getColorName(rgb).name
exists; guard against getColorName returning a string by checking the return
value from getColorName(rgb) inside colorNameFn (e.g., test typeof result ===
'object' && 'name' in result) and return the object.name when present, otherwise
fall back to a sensible string (the returned string/hex or a formatted rgb/empty
string). Update the ColorPicker instantiation (colorNameFn) to perform this
check so undefined names are avoided.

In `@WARP.md`:
- Line 124: Replace bare URLs in the WARP.md text (e.g., the line that currently
reads "Uses the `hue-hunter` npm package
(https://github.com/RobbieTheWagner/hue-hunter)") with a markdown link or
angle-bracketed URL; for example change the parenthetical raw URL to either
"[hue-hunter](https://github.com/RobbieTheWagner/hue-hunter)" or
"<https://github.com/RobbieTheWagner/hue-hunter>". Also update the other
occurrence noted (the similar bare URL later in the file) the same way so all
raw URLs conform to MD034.

Comment on lines +14 to +18
// Create ColorPicker instance with color naming
const picker = new ColorPicker({
colorNameFn: (rgb) => getColorName(rgb).name,
initialDiameter: 180,
initialSquareSize: 20,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E 'color-picker|color-picker.ts'

Repository: shipshapecode/swach

Length of output: 165


🏁 Script executed:

fd 'color-picker.ts' -t f

Repository: shipshapecode/swach

Length of output: 95


🏁 Script executed:

ls -la | head -20
fd 'electron-app' -t d

Repository: shipshapecode/swach

Length of output: 1305


🏁 Script executed:

git ls-files | grep '\.ts$' | head -30

Repository: shipshapecode/swach

Length of output: 903


🏁 Script executed:

find . -maxdepth 3 -type d | head -30

Repository: shipshapecode/swach

Length of output: 552


🏁 Script executed:

git ls-files | head -50

Repository: shipshapecode/swach

Length of output: 1285


🏁 Script executed:

git ls-files | grep -i color

Repository: shipshapecode/swach

Length of output: 816


🏁 Script executed:

rg 'getColorName' -t ts -t tsx -t js -t jsx

Repository: shipshapecode/swach

Length of output: 90


🏁 Script executed:

cat -n electron-app/src/color-picker.ts

Repository: shipshapecode/swach

Length of output: 2297


🏁 Script executed:

rg 'getColorName' -t ts -A 10

Repository: shipshapecode/swach

Length of output: 1063


🏁 Script executed:

cat -n types/nearest-color/index.d.ts

Repository: shipshapecode/swach

Length of output: 238


🏁 Script executed:

rg 'nearest-color' package.json -A 2

Repository: shipshapecode/swach

Length of output: 151


🌐 Web query:

nearest-color npm package documentation return type from method

💡 Result:

If you call from(colors) (or nearestFrom for the TypeScript variant) you get a function that, when passed a color, returns an object with this shape:

{ name: string, value: string (hex), rgb: { r: number, g: number, b: number }, distance: number }

(If you request multiple nearest colors the TypeScript/nearest-colors variant can return an array of such objects.) [1][2]

Sources:

  • nearest-color (npm README). [1]
  • nearest-colors (TypeScript variant README). [2]

Guard against getColorName returning a non-object value.

The nearest-color library's type definition indicates getColorName() can return either a ColorMatch object or a string. If it returns a string (hex value), accessing .name will return undefined rather than a color name string. The ColorPicker may behave unexpectedly or fail silently with undefined names.

🛡️ Suggested guard
-const picker = new ColorPicker({
-  colorNameFn: (rgb) => getColorName(rgb).name,
+const picker = new ColorPicker({
+  colorNameFn: (rgb) => {
+    const match = getColorName(rgb as { r: number; g: number; b: number });
+    return match?.name ?? 'Unknown';
+  },
   initialDiameter: 180,
   initialSquareSize: 20,
 });
📝 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.

Suggested change
// Create ColorPicker instance with color naming
const picker = new ColorPicker({
colorNameFn: (rgb) => getColorName(rgb).name,
initialDiameter: 180,
initialSquareSize: 20,
// Create ColorPicker instance with color naming
const picker = new ColorPicker({
colorNameFn: (rgb) => {
const match = getColorName(rgb as { r: number; g: number; b: number });
return match?.name ?? 'Unknown';
},
initialDiameter: 180,
initialSquareSize: 20,
🤖 Prompt for AI Agents
In `@electron-app/src/color-picker.ts` around lines 14 - 18, The colorNameFn
currently assumes getColorName(rgb).name exists; guard against getColorName
returning a string by checking the return value from getColorName(rgb) inside
colorNameFn (e.g., test typeof result === 'object' && 'name' in result) and
return the object.name when present, otherwise fall back to a sensible string
(the returned string/hex or a formatted rgb/empty string). Update the
ColorPicker instantiation (colorNameFn) to perform this check so undefined names
are avoided.

Comment thread WARP.md
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.github/workflows/ember.yml:
- Line 16: Remove the obsolete Rust toolchain action by deleting the workflow
step that contains the string "uses: dtolnay/rust-toolchain@stable"; this line
should be removed from all CI workflow YAMLs (ember.yml, electron.yml, lint.yml,
release.yml) so the workflows no longer invoke the Rust toolchain action and
avoid unnecessary CI overhead.

In @.github/workflows/release.yml:
- Line 15: Remove the unnecessary Rust toolchain setup steps by deleting any
workflow steps that use "uses: dtolnay/rust-toolchain@stable" (both occurrences
referenced in the diff) from the release workflow; ensure only Node.js/pnpm
setup steps remain (do not add new Rust-related actions or files), and verify
the job continues to run with the existing Node/pnpm steps and environment
variables unchanged.

Comment thread .github/workflows/ember.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
package.json (1)

170-170: nearest-color should be in dependencies, not devDependencies.

The nearest-color package is imported in both electron-app/src/color-picker.ts (Electron main process) and app/services/nearest-color.ts (Ember app service), which are production code. Placing it in devDependencies may cause the packaged application to fail if the bundler doesn't include dev dependencies.

🐛 Proposed fix

Move nearest-color from devDependencies to dependencies:

     "color-name-list": "^14.27.0",
     "decorator-transforms": "^2.3.1",
+    "nearest-color": "^0.4.4",
     "electron-dl": "^4.0.0",

And remove from devDependencies:

-    "nearest-color": "^0.4.4",
     "postcss": "^8.5.6",
♻️ Duplicate comments (1)
electron-app/src/color-picker.ts (1)

16-21: Guard against getColorName returning a non-object value.

As flagged previously, the nearest-color library can return either a ColorMatch object or potentially undefined/falsy when no match is found. Accessing .name directly may result in undefined being passed to the picker.

🛡️ Suggested guard
 const picker = new ColorPicker({
-  colorNameFn: (rgb) => getColorName(rgb).name,
+  colorNameFn: (rgb) => {
+    const match = getColorName(rgb as { r: number; g: number; b: number });
+    return match?.name ?? 'Unknown';
+  },
   initialDiameter: 180,
   initialSquareSize: 20,
 });
🧹 Nitpick comments (1)
electron-app/src/color-picker.ts (1)

36-38: Redundant hideWindow call when window is unavailable.

The else branch executes when mb.window is falsy or destroyed, yet it still calls mb.hideWindow(). This is at best redundant and at worst could cause unexpected behavior if the menubar implementation doesn't handle this gracefully.

♻️ Consider removing the else branch
     if (mb.window && !mb.window.isDestroyed()) {
       const hidePromise = new Promise<void>((resolve) => {
         if (mb.window?.isVisible()) {
           mb.window?.once('hide', () => resolve());
           mb.hideWindow();
         } else {
           resolve();
         }
       });
       await hidePromise;
-    } else {
-      mb.hideWindow();
     }

@RobbieTheWagner RobbieTheWagner merged commit 65906d3 into main Jan 22, 2026
4 checks passed
@RobbieTheWagner RobbieTheWagner deleted the hue-hunter branch January 22, 2026 03:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant