-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix: Windows PATH and terminal DPI scaling improvements #1262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
fix: Windows PATH and terminal DPI scaling improvements #1262
Conversation
Windows Bash sessions were missing many system tools (ddev, mkcert, etc.) because they only inherited a limited COMMON_BIN_PATHS list. This adds: Backend (core/auth.py): - Pass full Windows PATH to SDK via get_sdk_env_vars() - Ensures Bash has access to all installed Windows tools Backend (core/platform): - Add get_windows_system_path() helper function Frontend (env-utils.ts): - Extend COMMON_BIN_PATHS for Windows with ddev and system paths - Merge with upstream Node.js paths Fixes issue where Windows tools were unavailable in Bash despite being in system PATH.
Terminal text appeared blurry or too small on Windows with display scaling (125%, 150%, 200%) because fontSize was fixed at 13px regardless of DPI. Changes: - Calculate fontSize based on window.devicePixelRatio - Use Math.sqrt() scaling for smoother adjustment - Minimum fontSize of 11px to maintain readability - Listen for DPI changes (monitor switching, scaling changes) - Dynamically update fontSize and re-fit terminal on DPI change This fixes terminal text rendering on high-DPI Windows displays and when moving windows between monitors with different scaling settings.
|
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughAdds Windows-specific PATH exposure in the backend, expands Windows candidate PATH entries in the frontend, and makes the terminal DPI-aware by dynamically adjusting font size and refitting on scale changes. Changes
Sequence Diagram(s)(omitted) Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
Summary of ChangesHello @mateuszruszkowski, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the developer experience for Windows users by resolving two critical issues. It ensures that Windows Bash sessions have complete access to system-wide tools by correctly propagating the full system PATH, and it introduces dynamic DPI-aware font scaling for the terminal, providing a consistent and readable interface across various display configurations. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
|
I have read the CLA Document and I hereby sign the CLA |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 Thanks for your first PR!
A maintainer will review it soon. Please make sure:
- Your branch is synced with
develop - CI checks pass
- You've followed our contribution guide
Welcome to the Auto Claude community!
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| // Calculate optimal font size based on device pixel ratio (Windows DPI scaling) | ||
| // devicePixelRatio > 1 indicates high-DPI displays (125%, 150%, 200% scaling) | ||
| // Base size: 13px for 100% scaling (devicePixelRatio = 1) | ||
| const baseFontSize = 13; | ||
| const scaleFactor = window.devicePixelRatio || 1; |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
|
|
||
| // Handle DPI/scale changes (Windows display scaling, moving between monitors) | ||
| useEffect(() => { | ||
| const handleDPIChange = debounce(() => { |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces two valuable improvements for Windows users: providing the full system PATH to Bash sessions and implementing DPI-aware font scaling in the terminal. These changes will significantly enhance the developer experience.
My review focuses on refining the implementation. I've suggested reusing an existing helper function in the Python backend to simplify the PATH retrieval logic. For the frontend, I've identified a bug in the DPI change handler that prevents it from detecting subsequent DPI changes and proposed a fix. I've also pointed out some opportunities to improve code clarity and reduce duplication in the font scaling calculations. Overall, great work on tackling these platform-specific issues.
| useEffect(() => { | ||
| const handleDPIChange = debounce(() => { | ||
| if (xtermRef.current) { | ||
| const baseFontSize = 13; | ||
| const scaleFactor = window.devicePixelRatio || 1; | ||
| const adjustedFontSize = scaleFactor > 1 | ||
| ? Math.max(baseFontSize * (1 / Math.sqrt(scaleFactor)), 11) | ||
| : baseFontSize; | ||
|
|
||
| // Update font size dynamically | ||
| xtermRef.current.options.fontSize = Math.round(adjustedFontSize); | ||
|
|
||
| // Trigger a fit to recalculate terminal dimensions with new font size | ||
| if (fitAddonRef.current && terminalRef.current) { | ||
| const rect = terminalRef.current.getBoundingClientRect(); | ||
| if (rect.width > 0 && rect.height > 0) { | ||
| fitAddonRef.current.fit(); | ||
| } | ||
| } | ||
| } | ||
| }, 200); // Debounce DPI changes to avoid excessive recalculations | ||
|
|
||
| // Listen for DPI/scale changes via matchMedia | ||
| // This fires when user moves window between monitors with different DPI | ||
| // or changes Windows display scaling settings | ||
| const mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); | ||
|
|
||
| // Modern API: addEventListener | ||
| if (mediaQuery.addEventListener) { | ||
| mediaQuery.addEventListener('change', handleDPIChange); | ||
| return () => mediaQuery.removeEventListener('change', handleDPIChange); | ||
| } | ||
|
|
||
| // Fallback for older browsers: addListener (deprecated) | ||
| if (mediaQuery.addListener) { | ||
| mediaQuery.addListener(handleDPIChange); | ||
| return () => mediaQuery.removeListener?.(handleDPIChange); | ||
| } | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This DPI change handler has a bug: it only detects the first DPI change. The mediaQuery is created once with the initial devicePixelRatio and is not updated after a change, so subsequent DPI changes are missed.
To fix this, the useEffect hook needs to re-run to create a new MediaQueryList with the updated devicePixelRatio. You can achieve this by introducing a state variable to track the pixel ratio.
- Add state to your component:
const [dpr, setDpr] = useState(window.devicePixelRatio || 1); - Update this
useEffectto depend ondprand update it within the handler. Using{ once: true }for the event listener simplifies cleanup and re-registration.
Additionally, the font size calculation logic is duplicated from the initialization effect. When refactoring, please extract this logic into a shared helper function to follow the DRY principle.
// Handle DPI/scale changes (Windows display scaling, moving between monitors)
useEffect(() => {
const handleDPIChange = debounce(() => {
if (xtermRef.current) {
const newScaleFactor = window.devicePixelRatio || 1;
const baseFontSize = 13;
const adjustedFontSize = newScaleFactor > 1
? Math.max(baseFontSize * (1 / Math.sqrt(newScaleFactor)), 11)
: baseFontSize;
xtermRef.current.options.fontSize = Math.round(adjustedFontSize);
if (fitAddonRef.current && terminalRef.current) {
const rect = terminalRef.current.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
fitAddonRef.current.fit();
}
}
setDpr(newScaleFactor);
}
}, 200);
const mediaQuery = window.matchMedia(`(resolution: ${dpr}dppx)`);
mediaQuery.addEventListener('change', handleDPIChange, { once: true });
return () => {
mediaQuery.removeEventListener('change', handleDPIChange);
};
}, [dpr]);| # Get PATH from environment - Windows is case-insensitive for env vars | ||
| path = os.environ.get("PATH") or os.environ.get("Path") or os.environ.get("path") | ||
| return path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To improve maintainability and reduce code duplication, this logic for retrieving the PATH environment variable on Windows can be simplified by using the existing get_env_var helper function from this same file, which already handles case-insensitive lookups correctly.
| # Get PATH from environment - Windows is case-insensitive for env vars | |
| path = os.environ.get("PATH") or os.environ.get("Path") or os.environ.get("path") | |
| return path | |
| # Use the existing helper for case-insensitive environment variable access. | |
| return get_env_var("PATH") |
| // Adjust font size inversely to pixel ratio to maintain visual consistency | ||
| // For 150% scaling (1.5x), use 13 * (1 / 1.5) ≈ 8.67 logical pixels |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is misleading as it describes a direct inverse scaling (1 / 1.5), but the implementation on line 57 uses 1 / Math.sqrt(scaleFactor). To prevent confusion for future maintainers, please update the comment to accurately reflect the code's logic, which uses a square root to make the font size reduction less aggressive.
| // Adjust font size inversely to pixel ratio to maintain visual consistency | |
| // For 150% scaling (1.5x), use 13 * (1 / 1.5) ≈ 8.67 logical pixels | |
| // Adjust font size using the square root of the scale factor to make the | |
| // font size reduction less aggressive on high-DPI displays. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@apps/backend/core/platform/__init__.py`:
- Around line 519-535: The get_windows_system_path function duplicates
case-insensitive env lookup; replace the manual
os.environ.get("PATH")/("Path")/("path") logic by calling the existing helper
get_env_var("PATH") so behavior is centralized; update the return to use
get_env_var("PATH") inside get_windows_system_path and keep the early
is_windows() check intact.
In `@apps/frontend/src/main/env-utils.ts`:
- Around line 111-127: COMMON_BIN_PATHS currently contains hardcoded Windows
roots; replace those literals by constructing Windows paths from environment
variables and platform helpers: when process.platform === 'win32', read
ProgramFiles, ProgramFiles(x86) (or fallback to ProgramFiles), ProgramData,
SystemRoot, LOCALAPPDATA, APPDATA and USERPROFILE, use path.join or path.resolve
to build the specific subpaths (e.g. join(ProgramFiles, 'Git', 'cmd'),
join(ProgramFiles, 'nodejs'), join(LOCALAPPDATA, 'Programs', 'nodejs'),
join(APPDATA, 'npm'), join(USERPROFILE, '.local', 'bin'), join(ProgramData,
'chocolatey', 'bin'), etc.), expand '~' against USERPROFILE for POSIX-style
entries, filter out undefined or non-existent env values, and only include these
constructed entries in COMMON_BIN_PATHS so platform-specific roots are not
hardcoded.
In `@apps/frontend/src/renderer/components/terminal/useXterm.ts`:
- Around line 333-372: Guard against window.matchMedia being undefined and add a
resize fallback, then re-register the media query whenever DPI changes: inside
the effect, check if window.matchMedia exists before using it and fall back to
listening to window.resize; modify the handleDPIChange routine (and its
debounce) to, after adjusting xtermRef.current.options.fontSize and calling
fitAddonRef.current.fit() (using terminalRef to check bounds), recreate the
mediaQuery using the latest window.devicePixelRatio and reattach its 'change'
listener so the query isn't stale; ensure you remove the correct listeners on
cleanup (use removeEventListener when added and removeListener fallback for
older APIs) and reference the existing symbols handleDPIChange, xtermRef,
fitAddonRef, terminalRef, and mediaQuery when updating the logic.
| def get_windows_system_path() -> str | None: | ||
| """ | ||
| Get the full Windows system PATH environment variable. | ||
| This retrieves the complete PATH from the Windows environment, including | ||
| all user and system paths. Useful for ensuring Bash sessions have access | ||
| to all installed Windows tools. | ||
| Returns: | ||
| Full PATH string on Windows, None on other platforms | ||
| """ | ||
| if not is_windows(): | ||
| return None | ||
|
|
||
| # Get PATH from environment - Windows is case-insensitive for env vars | ||
| path = os.environ.get("PATH") or os.environ.get("Path") or os.environ.get("path") | ||
| return path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Reuse get_env_var("PATH") for consistency.
This duplicates the Windows case-insensitive env lookup logic already in get_env_var. Consider reusing that helper to keep behavior centralized.
♻️ Suggested refactor
- path = os.environ.get("PATH") or os.environ.get("Path") or os.environ.get("path")
- return path
+ return get_env_var("PATH")🤖 Prompt for AI Agents
In `@apps/backend/core/platform/__init__.py` around lines 519 - 535, The
get_windows_system_path function duplicates case-insensitive env lookup; replace
the manual os.environ.get("PATH")/("Path")/("path") logic by calling the
existing helper get_env_var("PATH") so behavior is centralized; update the
return to use get_env_var("PATH") inside get_windows_system_path and keep the
early is_windows() check intact.
| // User-local binaries | ||
| '~/.local/bin', // User-local binaries (Claude CLI installed here) | ||
| // Common development tools | ||
| 'C:\\Program Files\\Git\\cmd', | ||
| 'C:\\Program Files\\Git\\bin', | ||
| 'C:\\Program Files\\GitHub CLI', | ||
| 'C:\\Program Files\\ddev', // DDEV local development | ||
| // Node.js and npm paths - critical for packaged Electron apps that don't inherit full PATH | ||
| 'C:\\Program Files\\nodejs', // Standard Node.js installer (64-bit) | ||
| 'C:\\Program Files (x86)\\nodejs', // 32-bit Node.js on 64-bit Windows | ||
| '~\\AppData\\Local\\Programs\\nodejs', // NVM for Windows / user install | ||
| '~\\AppData\\Roaming\\npm', // npm global scripts (claude.cmd lives here) | ||
| '~\\scoop\\apps\\nodejs\\current', // Scoop package manager | ||
| 'C:\\ProgramData\\chocolatey\\bin', // Chocolatey package manager | ||
| // Common system paths | ||
| 'C:\\Windows\\System32', | ||
| 'C:\\Windows', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid hardcoded Windows roots in COMMON_BIN_PATHS.
These new entries hardcode C:\... roots, which can break on non-default installs/localized Windows and conflicts with the platform-path guideline. Please derive these from environment variables (ProgramFiles, ProgramData, SystemRoot, LOCALAPPDATA, APPDATA, USERPROFILE) and filter missing values. Consider using platform path helpers for joins.
🔧 Suggested refactor pattern
+const windowsBinPaths = (() => {
+ const homeDir = os.homedir();
+ const programFiles = process.env.ProgramFiles;
+ const programFilesX86 = process.env['ProgramFiles(x86)'];
+ const programData = process.env.ProgramData;
+ const systemRoot = process.env.SystemRoot;
+ const localAppData = process.env.LOCALAPPDATA;
+ const appData = process.env.APPDATA;
+
+ return [
+ path.join(homeDir, '.local', 'bin'),
+ ...(programFiles ? [
+ path.join(programFiles, 'Git', 'cmd'),
+ path.join(programFiles, 'Git', 'bin'),
+ path.join(programFiles, 'GitHub CLI'),
+ path.join(programFiles, 'ddev'),
+ path.join(programFiles, 'nodejs'),
+ ] : []),
+ ...(programFilesX86 ? [path.join(programFilesX86, 'nodejs')] : []),
+ ...(localAppData ? [path.join(localAppData, 'Programs', 'nodejs')] : []),
+ ...(appData ? [path.join(appData, 'npm')] : []),
+ path.join(homeDir, 'scoop', 'apps', 'nodejs', 'current'),
+ ...(programData ? [path.join(programData, 'chocolatey', 'bin')] : []),
+ ...(systemRoot ? [path.join(systemRoot, 'System32'), systemRoot] : []),
+ ];
+})();
+
export const COMMON_BIN_PATHS: Record<string, string[]> = {
darwin: [
...
],
linux: [
...
],
- win32: [
- // User-local binaries
- '~/.local/bin',
- // Common development tools
- 'C:\\Program Files\\Git\\cmd',
- 'C:\\Program Files\\Git\\bin',
- 'C:\\Program Files\\GitHub CLI',
- 'C:\\Program Files\\ddev',
- // Node.js and npm paths - critical for packaged Electron apps that don't inherit full PATH
- 'C:\\Program Files\\nodejs',
- 'C:\\Program Files (x86)\\nodejs',
- '~\\AppData\\Local\\Programs\\nodejs',
- '~\\AppData\\Roaming\\npm',
- '~\\scoop\\apps\\nodejs\\current',
- 'C:\\ProgramData\\chocolatey\\bin',
- // Common system paths
- 'C:\\Windows\\System32',
- 'C:\\Windows',
- ],
+ win32: windowsBinPaths,
};As per coding guidelines, avoid hardcoded platform-specific paths.
🤖 Prompt for AI Agents
In `@apps/frontend/src/main/env-utils.ts` around lines 111 - 127, COMMON_BIN_PATHS
currently contains hardcoded Windows roots; replace those literals by
constructing Windows paths from environment variables and platform helpers: when
process.platform === 'win32', read ProgramFiles, ProgramFiles(x86) (or fallback
to ProgramFiles), ProgramData, SystemRoot, LOCALAPPDATA, APPDATA and
USERPROFILE, use path.join or path.resolve to build the specific subpaths (e.g.
join(ProgramFiles, 'Git', 'cmd'), join(ProgramFiles, 'nodejs'),
join(LOCALAPPDATA, 'Programs', 'nodejs'), join(APPDATA, 'npm'),
join(USERPROFILE, '.local', 'bin'), join(ProgramData, 'chocolatey', 'bin'),
etc.), expand '~' against USERPROFILE for POSIX-style entries, filter out
undefined or non-existent env values, and only include these constructed entries
in COMMON_BIN_PATHS so platform-specific roots are not hardcoded.
| // Handle DPI/scale changes (Windows display scaling, moving between monitors) | ||
| useEffect(() => { | ||
| const handleDPIChange = debounce(() => { | ||
| if (xtermRef.current) { | ||
| const baseFontSize = 13; | ||
| const scaleFactor = window.devicePixelRatio || 1; | ||
| const adjustedFontSize = scaleFactor > 1 | ||
| ? Math.max(baseFontSize * (1 / Math.sqrt(scaleFactor)), 11) | ||
| : baseFontSize; | ||
|
|
||
| // Update font size dynamically | ||
| xtermRef.current.options.fontSize = Math.round(adjustedFontSize); | ||
|
|
||
| // Trigger a fit to recalculate terminal dimensions with new font size | ||
| if (fitAddonRef.current && terminalRef.current) { | ||
| const rect = terminalRef.current.getBoundingClientRect(); | ||
| if (rect.width > 0 && rect.height > 0) { | ||
| fitAddonRef.current.fit(); | ||
| } | ||
| } | ||
| } | ||
| }, 200); // Debounce DPI changes to avoid excessive recalculations | ||
|
|
||
| // Listen for DPI/scale changes via matchMedia | ||
| // This fires when user moves window between monitors with different DPI | ||
| // or changes Windows display scaling settings | ||
| const mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); | ||
|
|
||
| // Modern API: addEventListener | ||
| if (mediaQuery.addEventListener) { | ||
| mediaQuery.addEventListener('change', handleDPIChange); | ||
| return () => mediaQuery.removeEventListener('change', handleDPIChange); | ||
| } | ||
|
|
||
| // Fallback for older browsers: addListener (deprecated) | ||
| if (mediaQuery.addListener) { | ||
| mediaQuery.addListener(handleDPIChange); | ||
| return () => mediaQuery.removeListener?.(handleDPIChange); | ||
| } | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let me check the test environment setup and any existing tests for this file
cd apps/frontend && find . -name "*useXterm*" -type f | head -20Repository: AndyMik90/Auto-Claude
Length of output: 173
🌐 Web query:
JSDOM window.matchMedia undefined test environment
💡 Result:
Cause: JSDOM (used by Jest) does not implement window.matchMedia, so calls fail with "window.matchMedia is not a function". [1][4]
Quick fixes
-
Add a global mock in your Jest setup file (setupFiles / setupTests) so it runs before your tested modules. Example:
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: query => ({
matches: false,
media: query,
onchange: null,
addListener: () => {},
removeListener: () => {},
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => {},
}),
});
(Put this in setupTests.ts or a file listed in jest.config.js -> setupFiles or setupFilesAfterEnv). [0][3][5] -
Use a ready-made polyfill/mock package (e.g. mq-polyfill or
@raymondwang/mock-match-media) and import it from your Jest setup so you can simulate media changes and event listeners. [2][1]
Notes
- If matchMedia is invoked at module import time, be sure the setup file is loaded before those modules (use setupFiles, not just per-test mocks). [4][5]
References
[0] Stack Overflow: Jest test fails: TypeError: window.matchMedia is not a function.
[1] @raymondwang/mock-match-media (npm / libraries.io).
[2] mq-polyfill / community suggestions (example workarounds).
[3] Blog example adding Object.defineProperty in setupTests.
[4] Jest docs: Mocking methods not implemented in JSDOM / Manual mocks.
🌐 Web query:
media query resolution change re-registration browser behavior
💡 Result:
Short answer
- Browsers update window.devicePixelRatio when the page/zoom/monitor DPI changes (e.g., dragging between displays). You can detect that by using matchMedia and its "change" event. [1][2]
Key behavior and practical pattern
- If you create a media query that checks an exact resolution (e.g. (resolution: 2dppx)), that MediaQueryList is only true for that value; when the DPR changes the MediaQueryList fires "change" once but you typically must create a new query based on the new devicePixelRatio if you want to keep monitoring future changes. MDN’s example shows re-registering a new matchMedia query each time. [1]
- In practice use either: (a) a range-based media query that stays relevant across DPRs, or (b) recreate a media-query listener when devicePixelRatio changes (read window.devicePixelRatio inside the handler and re-register). [1][2]
Caveats
- Implementation details and exact event timing can vary across browsers; testing on target browsers is recommended. [1]
Sources
[1] MDN — Window.devicePixelRatio / matchMedia example.
[2] StackOverflow discussion on devicePixelRatio changing when dragging between monitors.
🏁 Script executed:
# Check if there are any test files or existing documentation about this issue
git log --oneline --all -- "apps/frontend/src/renderer/components/terminal/useXterm.ts" | head -10Repository: AndyMik90/Auto-Claude
Length of output: 284
🏁 Script executed:
# Search for any CI/test failures related to matchMedia in the codebase
rg "matchMedia" --type ts --type tsx -B 2 -A 2Repository: AndyMik90/Auto-Claude
Length of output: 92
🏁 Script executed:
# Search for matchMedia usage without the file type filter
rg "matchMedia" -B 2 -A 2Repository: AndyMik90/Auto-Claude
Length of output: 3678
🏁 Script executed:
# Check the actual test file to see how it's set up
cat -n apps/frontend/src/renderer/components/terminal/__tests__/useXterm.test.ts | head -100Repository: AndyMik90/Auto-Claude
Length of output: 3563
🏁 Script executed:
# Check the jest config to see if there's a setup file
find . -name "jest.config.*" -o -name "setupTests.*" -o -name "setup.ts" | grep -E "(jest|setup)" | head -10Repository: AndyMik90/Auto-Claude
Length of output: 103
🏁 Script executed:
# Check the setup.ts file to see if matchMedia is mocked
cat -n apps/frontend/src/__tests__/setup.tsRepository: AndyMik90/Auto-Claude
Length of output: 4890
🏁 Script executed:
# Let me also check if there are any vitest configs
find . -name "vitest.config.*" -o -name "vite.config.*" | head -10Repository: AndyMik90/Auto-Claude
Length of output: 129
🏁 Script executed:
# Check the useXterm.ts file more completely to understand the full context
cat -n apps/frontend/src/renderer/components/terminal/useXterm.ts | sed -n '1,50p'Repository: AndyMik90/Auto-Claude
Length of output: 2431
🏁 Script executed:
# Get lines 333-372 specifically from useXterm.ts
sed -n '333,372p' apps/frontend/src/renderer/components/terminal/useXterm.tsRepository: AndyMik90/Auto-Claude
Length of output: 1704
🏁 Script executed:
# Check if there's a matchMedia mock anywhere in the codebase
rg "matchMedia.*value.*function|matchMedia.*mock|Object.defineProperty.*matchMedia" -A 5Repository: AndyMik90/Auto-Claude
Length of output: 47
🏁 Script executed:
# Check the vitest config to see if it uses setup files
cat -n apps/frontend/vitest.config.tsRepository: AndyMik90/Auto-Claude
Length of output: 1489
🏁 Script executed:
# Look at the test file to see if it tests the DPI change effect
cat -n apps/frontend/src/renderer/components/terminal/__tests__/useXterm.test.ts | grep -A 30 -B 5 "DPI\|matchMedia\|devicePixelRatio"Repository: AndyMik90/Auto-Claude
Length of output: 47
🏁 Script executed:
# Check the full test file to see what tests exist
cat -n apps/frontend/src/renderer/components/terminal/__tests__/useXterm.test.ts | tail -200Repository: AndyMik90/Auto-Claude
Length of output: 7081
🏁 Script executed:
# Search for any test that includes "DPI" or the specific effect
cat -n apps/frontend/src/renderer/components/terminal/__tests__/useXterm.test.ts | grep -i "dpi\|scale\|resize\|font" -B 2 -A 5Repository: AndyMik90/Auto-Claude
Length of output: 736
🏁 Script executed:
# Let me check if there's a mock for window.matchMedia in the setup file
grep -n "matchMedia" apps/frontend/src/__tests__/setup.tsRepository: AndyMik90/Auto-Claude
Length of output: 47
🏁 Script executed:
# Check if there are any recent commits mentioning matchMedia or DPI issues
git log --oneline --all --grep="matchMedia\|DPI\|display scale" | head -20Repository: AndyMik90/Auto-Claude
Length of output: 327
🏁 Script executed:
# Look for any error reports or issues in comments
rg "matchMedia.*undefined|is not a function" --type ts --type tsxRepository: AndyMik90/Auto-Claude
Length of output: 92
🏁 Script executed:
# Check if the test file actually runs the DPI effect
cat -n apps/frontend/src/renderer/components/terminal/__tests__/useXterm.test.ts | head -200Repository: AndyMik90/Auto-Claude
Length of output: 7262
🏁 Script executed:
# Let me verify the actual behavior by checking the full useXterm.ts including dependencies
wc -l apps/frontend/src/renderer/components/terminal/useXterm.tsRepository: AndyMik90/Auto-Claude
Length of output: 127
🏁 Script executed:
# Check other files using matchMedia to see how they handle it
rg "window.matchMedia" -B 3 -A 3 --type ts --type tsxRepository: AndyMik90/Auto-Claude
Length of output: 92
🏁 Script executed:
# Search for matchMedia usage in all files
rg "window.matchMedia" -B 3 -A 3Repository: AndyMik90/Auto-Claude
Length of output: 4558
🏁 Script executed:
# Check how other components handle matchMedia - look for guards or setup
rg "matchMedia" apps/frontend/src -B 2 -A 2Repository: AndyMik90/Auto-Claude
Length of output: 3678
🏁 Script executed:
# Look at the recent DPI commit to understand what was added
git show 18485c3 --statRepository: AndyMik90/Auto-Claude
Length of output: 50377
🏁 Script executed:
# Let me see if there's actual test failure evidence or if this is a hypothetical issue
git log --oneline -20 --all -- "apps/frontend/src/renderer/components/terminal/__tests__/useXterm.test.ts"Repository: AndyMik90/Auto-Claude
Length of output: 174
🏁 Script executed:
# Check if there's a vitest environment-specific mock needed
cat apps/frontend/vitest.config.ts | grep -A 5 -B 5 "environment"Repository: AndyMik90/Auto-Claude
Length of output: 835
🏁 Script executed:
# Let me verify if the test file actually runs and what errors it produces
cat -n apps/frontend/src/renderer/components/terminal/__tests__/useXterm.test.ts | grep -A 50 "describe.*useXterm"Repository: AndyMik90/Auto-Claude
Length of output: 2018
Guard window.matchMedia and re-register the query to keep DPI updates working.
window.matchMedia is undefined in test/JSDOM environments (causing test failures), and resolution-based media queries only fire once because they check an exact devicePixelRatio value. When DPI changes, the query becomes stale and won't fire again. Add a guard with a resize fallback and re-register the media query after each DPI change with the updated devicePixelRatio.
🐛 Suggested fix
useEffect(() => {
const handleDPIChange = debounce(() => {
if (xtermRef.current) {
const baseFontSize = 13;
const scaleFactor = window.devicePixelRatio || 1;
const adjustedFontSize = scaleFactor > 1
? Math.max(baseFontSize * (1 / Math.sqrt(scaleFactor)), 11)
: baseFontSize;
// Update font size dynamically
xtermRef.current.options.fontSize = Math.round(adjustedFontSize);
// Trigger a fit to recalculate terminal dimensions with new font size
if (fitAddonRef.current && terminalRef.current) {
const rect = terminalRef.current.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
fitAddonRef.current.fit();
}
}
}
}, 200); // Debounce DPI changes to avoid excessive recalculations
+ if (typeof window.matchMedia !== 'function') {
+ window.addEventListener('resize', handleDPIChange);
+ return () => window.removeEventListener('resize', handleDPIChange);
+ }
+
+ let mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
+ const onChange = () => {
+ handleDPIChange();
+ // Re-register to keep tracking subsequent DPI changes
+ mediaQuery.removeEventListener?.('change', onChange);
+ mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
+ mediaQuery.addEventListener?.('change', onChange);
+ };
- const mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
// Modern API: addEventListener
if (mediaQuery.addEventListener) {
- mediaQuery.addEventListener('change', handleDPIChange);
- return () => mediaQuery.removeEventListener('change', handleDPIChange);
+ mediaQuery.addEventListener('change', onChange);
+ return () => mediaQuery.removeEventListener('change', onChange);
}
// Fallback for older browsers: addListener (deprecated)
if (mediaQuery.addListener) {
- mediaQuery.addListener(handleDPIChange);
- return () => mediaQuery.removeListener?.(handleDPIChange);
+ mediaQuery.addListener(onChange);
+ return () => mediaQuery.removeListener?.(onChange);
}
}, []);🧰 Tools
🪛 GitHub Check: test-frontend (macos-latest)
[failure] 359-359: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable copy shortcuts on Linux (both CTRL+C and CTRL+SHIFT+C)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable paste shortcuts on Linux (both CTRL+V and CTRL+SHIFT+V)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable paste shortcuts on Windows (CTRL+V)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard error handling without breaking terminal > should continue terminal operation after clipboard error
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > keyboard event propagation > should maintain correct handler ordering for existing shortcuts
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > keyboard event propagation > should prevent copy/paste events from interfering with other shortcuts
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard read with xterm paste integration > should not paste when clipboard is empty
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard read with xterm paste integration > should integrate clipboard.readText() with xterm.paste()
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > xterm.js selection API integration with clipboard write > should not call getSelection when hasSelection returns false
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > xterm.js selection API integration with clipboard write > should integrate xterm.hasSelection() with clipboard write
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
🪛 GitHub Check: test-frontend (ubuntu-latest)
[failure] 359-359: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable copy shortcuts on Linux (both CTRL+C and CTRL+SHIFT+C)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable paste shortcuts on Linux (both CTRL+V and CTRL+SHIFT+V)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable paste shortcuts on Windows (CTRL+V)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard error handling without breaking terminal > should continue terminal operation after clipboard error
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > keyboard event propagation > should maintain correct handler ordering for existing shortcuts
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > keyboard event propagation > should prevent copy/paste events from interfering with other shortcuts
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard read with xterm paste integration > should not paste when clipboard is empty
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard read with xterm paste integration > should integrate clipboard.readText() with xterm.paste()
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > xterm.js selection API integration with clipboard write > should not call getSelection when hasSelection returns false
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 359-359: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > xterm.js selection API integration with clipboard write > should integrate xterm.hasSelection() with clipboard write
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:359:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
🤖 Prompt for AI Agents
In `@apps/frontend/src/renderer/components/terminal/useXterm.ts` around lines 333
- 372, Guard against window.matchMedia being undefined and add a resize
fallback, then re-register the media query whenever DPI changes: inside the
effect, check if window.matchMedia exists before using it and fall back to
listening to window.resize; modify the handleDPIChange routine (and its
debounce) to, after adjusting xtermRef.current.options.fontSize and calling
fitAddonRef.current.fit() (using terminalRef to check bounds), recreate the
mediaQuery using the latest window.devicePixelRatio and reattach its 'change'
listener so the query isn't stale; ensure you remove the correct listeners on
cleanup (use removeEventListener when added and removeListener fallback for
older APIs) and reference the existing symbols handleDPIChange, xtermRef,
fitAddonRef, terminalRef, and mediaQuery when updating the logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| if (mediaQuery.addListener) { | ||
| mediaQuery.addListener(handleDPIChange); | ||
| return () => mediaQuery.removeListener?.(handleDPIChange); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DPI change detection misses multi-monitor transitions
Medium Severity
The matchMedia query is created once with the initial devicePixelRatio, but the change event only fires when the match state changes. Moving between two DPI values that are both different from the initial one (e.g., 2x → 1x when started at 1.5x) won't trigger the handler because the query remains in "not matching" state throughout. The correct pattern requires recreating the media query after each DPI change using { once: true } and re-registering a new listener for the updated ratio.
| win32: [ | ||
| // Windows usually handles PATH better, but we can add common locations | ||
| // User-local binaries | ||
| '~/.local/bin', // User-local binaries (Claude CLI installed here) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Windows path uses inconsistent forward slash separator
Medium Severity
The newly added '~/.local/bin' path uses a forward slash, while all other Windows tilde-prefixed paths use backslashes (e.g., '~\\AppData\\Roaming\\npm'). After expansion with os.homedir() (which returns backslashes on Windows), this creates mixed separators like 'C:\\Users\\username/.local/bin'. The currentPathSet.has(p) check at line 238 uses string comparison, so this won't match an existing PATH entry 'C:\\Users\\username\\.local\\bin', causing potential duplicate PATH entries.
🤖 Auto Claude PR ReviewMerge Verdict: 🔴 BLOCKED
Blocked: 4 CI check(s) failing. Fix CI before merge. Risk Assessment
🚨 Blocking Issues (Must Fix)
Generated by Auto Claude PR Review This review identified blockers that must be resolved before merge. Generated by Auto Claude. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/frontend/src/renderer/components/terminal/useXterm.ts`:
- Around line 383-399: Guard against window.matchMedia being undefined and make
the DPI listener re-register the media query after DPI changes: in the effect
that creates mediaQuery (the variable mediaQuery) check if typeof
window.matchMedia === 'function' before using it and return early if not (or add
the provided JSDOM mock in test setup to unblock CI); modify handleDPIChange so
it removes listeners from the existing mediaQuery, recomputes a new mediaQuery
using the current window.devicePixelRatio, and attaches the appropriate listener
via addEventListener/addListener (and corresponding
removeEventListener/removeListener) so subsequent DPI changes (e.g., moving
between monitors) are detected; ensure cleanup returns a function that removes
listeners from whichever mediaQuery is currently registered.
♻️ Duplicate comments (2)
apps/frontend/src/renderer/components/terminal/useXterm.ts (2)
54-56: Comment does not match implementation.The comment describes direct inverse scaling (
1 / 1.5), but the code uses1 / Math.sqrt(scaleFactor). Update the comment to accurately reflect the square-root-based formula.
364-368: Extract duplicated font size calculation to a helper function.This calculation is duplicated from lines 52-58. Extract to a reusable helper to follow DRY and ensure consistency if the formula changes.
♻️ Suggested refactor
Add a helper function at the top of the file (after imports):
/** Calculate DPI-adjusted font size for terminal */ function getAdjustedFontSize(baseFontSize = 13, minFontSize = 11): number { const scaleFactor = window.devicePixelRatio || 1; const adjustedFontSize = scaleFactor > 1 ? Math.max(baseFontSize * (1 / Math.sqrt(scaleFactor)), minFontSize) : baseFontSize; return Math.round(adjustedFontSize); }Then use it in both locations:
- const baseFontSize = 13; - const scaleFactor = window.devicePixelRatio || 1; - const adjustedFontSize = scaleFactor > 1 - ? Math.max(baseFontSize * (1 / Math.sqrt(scaleFactor)), 11) - : baseFontSize; + const adjustedFontSize = getAdjustedFontSize();
| // Listen for DPI/scale changes via matchMedia | ||
| // This fires when user moves window between monitors with different DPI | ||
| // or changes Windows display scaling settings | ||
| const mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); | ||
|
|
||
| // Modern API: addEventListener | ||
| if (mediaQuery.addEventListener) { | ||
| mediaQuery.addEventListener('change', handleDPIChange); | ||
| return () => mediaQuery.removeEventListener('change', handleDPIChange); | ||
| } | ||
|
|
||
| // Fallback for older browsers: addListener (deprecated) | ||
| if (mediaQuery.addListener) { | ||
| mediaQuery.addListener(handleDPIChange); | ||
| return () => mediaQuery.removeListener?.(handleDPIChange); | ||
| } | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CI blocker: Guard matchMedia and re-register query after DPI changes.
Two issues cause test failures and multi-monitor bugs:
-
Test failure (CI blocker):
window.matchMediais undefined in JSDOM, causingTypeError: window.matchMedia is not a functionacross all test runs. -
Stale media query: The query is created once with the initial
devicePixelRatio. After the first DPI change fires, the query won't detect subsequent changes (e.g., moving window between monitors multiple times).
🐛 Proposed fix
// Handle DPI/scale changes (Windows display scaling, moving between monitors)
useEffect(() => {
+ // Guard for test environments where matchMedia is not available
+ if (typeof window.matchMedia !== 'function') {
+ // Fallback to resize listener for basic DPI change detection
+ const handleResize = debounce(() => {
+ if (xtermRef.current) {
+ xtermRef.current.options.fontSize = getAdjustedFontSize();
+ if (fitAddonRef.current && terminalRef.current) {
+ const rect = terminalRef.current.getBoundingClientRect();
+ if (rect.width > 0 && rect.height > 0) {
+ fitAddonRef.current.fit();
+ }
+ }
+ }
+ }, 200);
+ window.addEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+ }
+
const handleDPIChange = debounce(() => {
if (xtermRef.current) {
- const baseFontSize = 13;
- const scaleFactor = window.devicePixelRatio || 1;
- const adjustedFontSize = scaleFactor > 1
- ? Math.max(baseFontSize * (1 / Math.sqrt(scaleFactor)), 11)
- : baseFontSize;
-
- // Update font size dynamically
- xtermRef.current.options.fontSize = Math.round(adjustedFontSize);
+ xtermRef.current.options.fontSize = getAdjustedFontSize();
// Trigger a fit to recalculate terminal dimensions with new font size
if (fitAddonRef.current && terminalRef.current) {
const rect = terminalRef.current.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
fitAddonRef.current.fit();
}
}
}
}, 200); // Debounce DPI changes to avoid excessive recalculations
- // Listen for DPI/scale changes via matchMedia
- // This fires when user moves window between monitors with different DPI
- // or changes Windows display scaling settings
- const mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
-
- // Modern API: addEventListener
- if (mediaQuery.addEventListener) {
- mediaQuery.addEventListener('change', handleDPIChange);
- return () => mediaQuery.removeEventListener('change', handleDPIChange);
- }
-
- // Fallback for older browsers: addListener (deprecated)
- if (mediaQuery.addListener) {
- mediaQuery.addListener(handleDPIChange);
- return () => mediaQuery.removeListener?.(handleDPIChange);
- }
+ // Re-register pattern: create new query after each DPI change
+ let mediaQuery: MediaQueryList;
+ const onChange = () => {
+ handleDPIChange();
+ // Re-register with updated devicePixelRatio to detect subsequent changes
+ mediaQuery.removeEventListener('change', onChange);
+ mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
+ mediaQuery.addEventListener('change', onChange);
+ };
+
+ mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
+ mediaQuery.addEventListener('change', onChange);
+
+ return () => {
+ mediaQuery.removeEventListener('change', onChange);
+ };
}, []);Alternatively, add a matchMedia mock to the test setup file (apps/frontend/src/__tests__/setup.ts) to unblock CI immediately:
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: (query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: () => {},
removeListener: () => {},
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => false,
}),
});🧰 Tools
🪛 GitHub Check: test-frontend (macos-latest)
[failure] 386-386: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable copy shortcuts on Linux (both CTRL+C and CTRL+SHIFT+C)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable paste shortcuts on Linux (both CTRL+V and CTRL+SHIFT+V)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable paste shortcuts on Windows (CTRL+V)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard error handling without breaking terminal > should continue terminal operation after clipboard error
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > keyboard event propagation > should maintain correct handler ordering for existing shortcuts
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > keyboard event propagation > should prevent copy/paste events from interfering with other shortcuts
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard read with xterm paste integration > should not paste when clipboard is empty
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard read with xterm paste integration > should integrate clipboard.readText() with xterm.paste()
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > xterm.js selection API integration with clipboard write > should not call getSelection when hasSelection returns false
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > xterm.js selection API integration with clipboard write > should integrate xterm.hasSelection() with clipboard write
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
🪛 GitHub Check: test-frontend (ubuntu-latest)
[failure] 386-386: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable copy shortcuts on Linux (both CTRL+C and CTRL+SHIFT+C)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable paste shortcuts on Linux (both CTRL+V and CTRL+SHIFT+V)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/renderer/components/terminal/tests/useXterm.test.ts > useXterm keyboard handlers > Platform detection > should enable paste shortcuts on Windows (CTRL+V)
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard error handling without breaking terminal > should continue terminal operation after clipboard error
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > keyboard event propagation > should maintain correct handler ordering for existing shortcuts
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > keyboard event propagation > should prevent copy/paste events from interfering with other shortcuts
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard read with xterm paste integration > should not paste when clipboard is empty
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > clipboard read with xterm paste integration > should integrate clipboard.readText() with xterm.paste()
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > xterm.js selection API integration with clipboard write > should not call getSelection when hasSelection returns false
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
[failure] 386-386: src/tests/integration/terminal-copy-paste.test.ts > Terminal copy/paste integration > xterm.js selection API integration with clipboard write > should integrate xterm.hasSelection() with clipboard write
TypeError: window.matchMedia is not a function
❯ src/renderer/components/terminal/useXterm.ts:386:31
❯ Object.react_stack_bottom_frame ../../node_modules/react-dom/cjs/react-dom-client.development.js:25989:20
❯ runWithFiberInDEV ../../node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ commitHookEffectListMount ../../node_modules/react-dom/cjs/react-dom-client.development.js:13249:29
❯ commitHookPassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:13336:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15484:13
❯ recursivelyTraversePassiveMountEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:15439:11
❯ commitPassiveMountOnFiber ../../node_modules/react-dom/cjs/react-dom-client.development.js:15519:11
❯ flushPassiveEffects ../../node_modules/react-dom/cjs/react-dom-client.development.js:18432:9
❯ ../../node_modules/react-dom/cjs/react-dom-client.development.js:17923:15
🤖 Prompt for AI Agents
In `@apps/frontend/src/renderer/components/terminal/useXterm.ts` around lines 383
- 399, Guard against window.matchMedia being undefined and make the DPI listener
re-register the media query after DPI changes: in the effect that creates
mediaQuery (the variable mediaQuery) check if typeof window.matchMedia ===
'function' before using it and return early if not (or add the provided JSDOM
mock in test setup to unblock CI); modify handleDPIChange so it removes
listeners from the existing mediaQuery, recomputes a new mediaQuery using the
current window.devicePixelRatio, and attaches the appropriate listener via
addEventListener/addListener (and corresponding
removeEventListener/removeListener) so subsequent DPI changes (e.g., moving
between monitors) are detected; ensure cleanup returns a function that removes
listeners from whichever mediaQuery is currently registered.
| # in Bash because they're not in the limited COMMON_BIN_PATHS | ||
| if platform.system() == "Windows": | ||
| system_path = get_windows_system_path() | ||
| if system_path: | ||
| env["PATH"] = system_path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: On Windows, the code incorrectly copies the parent process's potentially limited PATH, which can prevent the subprocess from finding necessary system tools.
Severity: HIGH
Suggested Fix
Instead of copying os.environ.get("PATH"), the code should retrieve the true system PATH from the Windows environment, for example, by querying the registry or using ctypes.windll. This ensures the subprocess receives the complete system PATH, regardless of the parent process's environment.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: apps/backend/core/auth.py#L418-L422
Potential issue: On Windows, the code sets `env["PATH"]` for the SDK subprocess using
the parent Python process's `os.environ.get("PATH")`. Since the SDK merges environments
with `env` values overriding `os.environ`, this effectively replaces the subprocess's
inherited `PATH`. If the parent Python process is running in a constrained environment
(e.g., a virtualenv) with a limited `PATH`, this limited `PATH` will be passed to the
subprocess. This prevents the subprocess from accessing the full set of system tools,
which is contrary to the intended goal of the change.
Did we get this right? 👍 / 👎 to inform future reviews.
Base Branch
developbranch (required for all feature/fix PRs)main(hotfix only - maintainers)Description
This PR fixes two Windows-specific issues that significantly improve the developer experience:
Full system PATH for Bash sessions: Windows Bash sessions now have access to all installed system tools (ddev, mkcert, chocolatey, etc.) by passing the complete Windows PATH to the SDK.
DPI-aware terminal font scaling: Terminal text now scales correctly on high-DPI displays (125%, 150%, 200% scaling) and dynamically adjusts when moving windows between monitors with different DPI settings.
Related Issue
Fixes Windows users not being able to run installed system tools from Bash sessions and terminal text rendering issues on high-DPI displays.
Type of Change
Area
Commit Message Format
Follow conventional commits:
<type>: <subject>Types: feat, fix, docs, style, refactor, test, chore
Example:
feat: add user authentication systemChecklist
developbranchPlatform Testing Checklist
CRITICAL: This project supports Windows, macOS, and Linux. Platform-specific bugs are a common source of breakage.
platform/module instead of directprocess.platformchecksNote: Changes are Windows-specific and use the centralized platform module. CI will verify no regressions on macOS/Linux.
CI/Testing Requirements
Screenshots
N/A - No UI changes, backend/terminal rendering improvements only.
Feature Toggle
use_feature_nameBreaking Changes
Breaking: No
Details: Backward compatible improvements. No API changes.
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.