Skip to content

feat(#461): add dedicated display mode with large album art showcase#497

Open
Owie6789 wants to merge 9 commits into
Sandakan:release/4.0.0-alpha.5from
Owie6789:fix/461-display-mode
Open

feat(#461): add dedicated display mode with large album art showcase#497
Owie6789 wants to merge 9 commits into
Sandakan:release/4.0.0-alpha.5from
Owie6789:fix/461-display-mode

Conversation

@Owie6789

@Owie6789 Owie6789 commented May 27, 2026

Copy link
Copy Markdown

Summary

adds a new display mode — a dedicated full-screen visual showcase with large centered album art, oversized song metadata, and auto-hidden controls.

Root Causes

no display mode existed in nora. the closest option was fullscreen player which focuses on lyrics.

Changes by file

  • src/types/app.d.ts — add 'display' to PlayerTypes, add 4 display mode preferences
  • src/renderer/src/other/appReducer.tsx — add defaults, treat 'display' like 'full' in UPDATE_PLAYER_TYPE (skip IPC, go fullscreen)
  • src/main/main.ts — add 'display' case to changePlayerType (fullscreen)
  • src/renderer/src/index.tsx — route subscriber detects /display-player and calls changePlayerType('display')
  • src/renderer/src/routes/display-player/index.tsx — new route file
  • src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx — new component with blurred artwork background, 50vh album art, text-7xl title, text-3xl artist, auto-hidden controls via useMouseActiveState, seek bar, NavLink close button
  • src/renderer/src/routeTree.gen.ts — full route registration
  • src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx — add NavLink + context menu entry for display mode
  • src/renderer/src/assets/locales/en/en.json — add player.backToPlayer + player.openInDisplayMode

Summary by CodeRabbit

  • New Features

    • Added a Display Mode player with artwork, optional song info, seek/playback controls, and a back-to-player action.
    • New menu and toolbar option plus route/URL support to open Display Mode directly.
    • New localization strings and display-mode preferences (title/artist/controls visibility and idle timeout).
  • Bug Fixes / Accessibility

    • Prevents player windows from appearing off-screen by re-positioning them to a visible display.
    • Improved keyboard focus/tab behavior for main and secondary containers.

@Owie6789 Owie6789 mentioned this pull request May 27, 2026
@Owie6789

Owie6789 commented May 27, 2026

Copy link
Copy Markdown
Author

there was no visual showcase mode. fullscreen player is more or less lyrics-focused. so built a dedicated display mode with large centered album art (50vh), oversized song title (text-7xl), artist name (text-3xl), and auto-hidden controls via useMouseActiveState. close button uses NavLink to /main-player/home matching the existing fullscreen/mini player pattern. layout reuses the blurred artwork background pattern from FullScreenPlayer.

@Owie6789 Owie6789 changed the title fix(#461): add dedicated display mode with large album art showcase feat(#461): add dedicated display mode with large album art showcase May 27, 2026
@Owie6789

Owie6789 commented Jun 5, 2026

Copy link
Copy Markdown
Author

@CodeRabbit — comprehensive review request. Please do all of the following and report findings as inline review comments on the changed files (not in this thread), grouped by severity.

1. Careful code review

  • Walk every changed file end-to-end. For each function/hook/component, describe what it does, what it depends on, and what can break it.
  • Verify the diff actually implements the PR title and the linked issue. Flag scope creep and unrelated drive-by edits.
  • Check that all new state, events, IPC channels, persistence keys, and settings keys are wired through the full stack (UI → context/store → IPC → main process → disk).
  • Confirm the linked issue's acceptance criteria are met. If a criterion is not met, say so explicitly.

2. UI audit (only if the PR touches UI)

  • Render correctness: layout, alignment, overflow, clipping, hit targets, focus rings, dark/light theme, HiDPI.
  • Accessibility: keyboard nav, ARIA roles/labels, contrast, motion-reduce, screen reader announcements for state changes.
  • Interaction states: default / hover / focus / active / disabled / loading / error / empty. Any state missing is a bug.
  • Performance: unnecessary re-renders, missing memoization on heavy lists, large images not resized, layout thrash, debounce/throttle on scroll/resize.
  • Empty/zero/error states: what does the user see when there is no data, network is down, permission is denied, or the file is corrupt.
  • Cross-platform: Windows, macOS, Linux. Electron-specific quirks (frameless regions, title bar drag, safe-area on multi-monitor with different DPI).

3. Production-readiness verification

  • No console.log / debugger / commented-out code left in.
  • No hardcoded paths, secrets, dev URLs, or fake TODO placeholders.
  • Error handling: every async path has a real catch that surfaces something to the user or to telemetry. No swallowed rejections.
  • Migrations / schema changes: if a persisted shape changed, is there a migration that does not lose user data?
  • Backwards compatibility with existing user data and settings. If something is intentionally breaking, is it called out in the PR description?
  • Memory leaks: listeners / observers / timers / intervals / AbortControllers are cleaned up on unmount.
  • Race conditions: concurrent clicks, double-submits, stale closures, optimistic UI rollback.
  • IPC contract: channel names match the renderer/main handshake, payloads are validated, and the main process guards against malformed messages.

4. Code smell sniff

  • Duplicated logic that should be a shared util / hook / component.
  • God components / functions over ~150 lines that should be split.
  • Boolean traps, magic numbers, stringly-typed APIs, leaky abstractions.
  • useEffect that should be a derived value, event handler, or ref.
  • Inline styles or className soup where a token or class would do.
  • Inconsistent naming or file/folder structure vs the rest of the repo.
  • Overly defensive code, dead code, as any / // @ts-ignore without a written justification.

5. Half-baked implementation detection

  • Feature appears to work in the happy path screenshot but lacks:
    • error path, empty path, permission-denied path, network-down path
    • loading / skeleton state
    • persistence (settings do not survive restart)
    • undo / cancel / back navigation
    • a11y (keyboard + screen reader)
    • i18n / RTL consideration if the rest of the app is localized
    • tests (unit + integration) for the new logic
  • Looks like a "first commit" version that ships a flag and stops — flag any feature gated behind an env var or if (false) branch.
  • "I'll add tests later" code with no test files added in this PR.

6. Required output from you

  • Inline review comments on the diff, file by file.
  • A short summary at the end listing: ✅ what is solid, ⚠️ what must change before merge, 💡 nice-to-haves.
  • For each ⚠️ item: severity (blocker / major / minor), the file:line, and the smallest possible fix direction.
  • If you find a blocker in the UI, also call it out in the summary so it is impossible to miss.

Please be thorough. I will be applying fixes from your review in a follow-up pass.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get PR info and diff
gh pr view 497 --repo Sandakan/Nora --json title,body,files,headRefName,baseRefName
echo "---DIFF---"
gh pr diff 497 --repo Sandakan/Nora

Length of output: 21218



Good, I have the full diff. Let me now gather more context from the existing codebase to do a thorough comparative analysis.

#!/bin/bash
# Get the FullScreenPlayer component for comparison
fd -i "FullScreenPlayer.tsx" --exec cat {}

echo "=== useMouseActiveState ==="
fd -i "useMouseActiveState*" --exec cat {}

echo "=== appReducer context around playerType persistence ==="
fd -i "appReducer.tsx" src/renderer/src/other/ --exec cat {}

echo "=== main.ts changePlayerType function context ==="
fd -i "main.ts" src/main/ --exec grep -n "changePlayerType\|PlayerTypes\|display\|fullscreen\|setFullScreen\|MAIN_WINDOW" {} | head -80

echo "=== Preferences interface / LocalStorage shape ==="
grep -n "displayModeIdleTimeout\|showSongTitleInDisplayMode\|showArtistNameInDisplayMode\|showControlsInDisplayMode\|LocalStorage\|Preferences" src/types/app.d.ts | head -60

echo "=== OtherSongControlsContainer full file ==="
fd -i "OtherSongControlsContainer.tsx" --exec cat {}

echo "=== existing NavLink component ==="
fd -i "NavLink.tsx" src/renderer/src/components/ --exec cat {}

echo "=== SeekBarSlider component ==="
fd -i "SeekBarSlider.tsx" src/renderer/src/components/ --exec cat {}

echo "=== index.tsx (router subscription) full ==="
cat src/renderer/src/index.tsx

echo "=== full appReducer UPDATE_PLAYER_TYPE case ==="
grep -n -A 20 "UPDATE_PLAYER_TYPE" src/renderer/src/other/appReducer.tsx

echo "=== fullscreen player route for comparison ==="
fd -i "index.tsx" src/renderer/src/routes/fullscreen-player/ --exec cat {}

echo "=== check for existing i18n locale files besides en ==="
fd -i "*.json" src/renderer/src/assets/locales/ --exec basename {} | sort

echo "=== check for any existing tests ==="
fd -i "*.test.*" src/ | head -20
fd -i "*.spec.*" src/ | head -20

echo "=== check how 'full' type is handled in appReducer ==="
grep -n -B5 -A30 "'full'" src/renderer/src/other/appReducer.tsx | head -60

echo "=== check how player type is persisted / saved ==="
grep -n "playerType\|saveUserSettings\|localStorage" src/renderer/src/other/appReducer.tsx | head -40

echo "=== check if display preferences have UI settings page ==="
grep -rn "showSongTitleInDisplayMode\|showArtistNameInDisplayMode\|showControlsInDisplayMode\|displayModeIdleTimeout" src/ | head -20

echo "=== check existing keyboard shortcuts / global keydown handlers ==="
grep -rn "Escape\|keydown\|keyup\|KeyboardEvent" src/renderer/src/components/DisplayModePlayer/ src/renderer/src/routes/display-player/ 2>/dev/null | head -20
grep -rn "Escape\|ArrowLeft\|ArrowRight\|Space" src/renderer/src/components/FullScreenPlayer/ 2>/dev/null | head -20

echo "=== check if DisplayModePlayer handles no-song state ==="
grep -n "currentSongData\|SongId\|songId" src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx

echo "=== check cursor hiding approach in existing players ==="
grep -rn "cursor-none\|cursor-auto\|isMouseActive\|hideCursor" src/renderer/src/ | head -20

echo "=== check MAIN_WINDOW constants ==="
grep -n "MAIN_WINDOW_MAX_SIZE_X\|MAIN_WINDOW_MAX_SIZE_Y\|MAIN_WINDOW_MIN_SIZE_X\|MAIN_WINDOW_MIN_SIZE_Y\|MAIN_WINDOW_ASPECT_RATIO" src/main/main.ts | head -20

echo "=== check how fullscreen exit (Esc) works in existing full player ==="
grep -rn "fullscreen-player\|full.*player\|setFullScreen\|leaveFullScreen" src/ --include="*.ts" --include="*.tsx" | grep -v "routeTree\|node_modules" | head -30

echo "=== check appUpdateContext for updatePlayerType ==="
grep -rn "updatePlayerType\|UPDATE_PLAYER_TYPE" src/renderer/src/ | head -20
</thinking>


```shell
#!/bin/bash
echo "=== FullScreenPlayer component ==="
fd -i "FullScreenPlayer.tsx" --exec cat {}

echo "=== useMouseActiveState hook ==="
fd -i "useMouseActiveState*" --exec cat {}

echo "=== main.ts changePlayerType full function ==="
grep -n -A 40 "export async function changePlayerType" src/main/main.ts

echo "=== appReducer UPDATE_PLAYER_TYPE + defaults ==="
grep -n -B2 -A 30 "UPDATE_PLAYER_TYPE\|LOCAL_STORAGE_DEFAULT" src/renderer/src/other/appReducer.tsx | head -120

echo "=== how playerType saved/persisted ==="
grep -n "playerType\|saveUserSettings\|localStorage" src/renderer/src/other/appReducer.tsx | head -30

echo "=== display preferences usage in codebase ==="
grep -rn "showSongTitleInDisplayMode\|showArtistNameInDisplayMode\|showControlsInDisplayMode\|displayModeIdleTimeout" src/ 2>/dev/null

echo "=== cursor hide on idle - existing patterns ==="
grep -rn "cursor-none\|isMouseActive" src/renderer/src/ 2>/dev/null | grep -v "node_modules" | head -20

echo "=== how full player exits fullscreen ==="
grep -rn "setFullScreen\|leaveFullscreen\|fullscreen" src/main/main.ts | head -20
grep -rn "fullscreen-player\|miniPlayer\|changePlayerType" src/renderer/src/index.tsx

echo "=== OtherSongControlsContainer full file ==="
cat src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx

echo "=== Display mode prefs settings page ==="
grep -rn "displayMode\|DisplayMode" src/renderer/src/components/ src/renderer/src/routes/ 2>/dev/null | grep -v "DisplayModePlayer\|routeTree\|node_modules" | head -20

echo "=== existing locale files ==="
fd -t f -e json . src/renderer/src/assets/locales/ | head -30

echo "=== check for console.log in new files ==="
grep -n "console\." src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
grep -n "console\." src/renderer/src/routes/display-player/index.tsx

echo "=== artworkPath on empty/no song ==="
grep -n "artworkPath\|currentSongData" src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx

echo "=== MAIN_WINDOW size constants ==="
grep -n "MAIN_WINDOW_MAX_SIZE\|MAIN_WINDOW_MIN_SIZE\|MAIN_WINDOW_ASPECT" src/main/main.ts | head -10

echo "=== index.tsx full ==="
cat src/renderer/src/index.tsx

echo "=== existing tests ==="
fd -e "test.ts" -e "test.tsx" -e "spec.ts" -e "spec.tsx" . src/ 2>/dev/null | head -10
#!/bin/bash
echo "=== FullScreenPlayer ==="
find . -name "FullScreenPlayer.tsx" -not -path "*/node_modules/*" -exec cat {} \;

echo "=== useMouseActiveState ==="
find . -name "useMouseActiveState*" -not -path "*/node_modules/*" -exec cat {} \;

echo "=== changePlayerType in main.ts ==="
sed -n '/export async function changePlayerType/,/^}/p' src/main/main.ts | head -60

echo "=== appReducer UPDATE_PLAYER_TYPE ==="
grep -n -A 20 "UPDATE_PLAYER_TYPE" src/renderer/src/other/appReducer.tsx

echo "=== LOCAL_STORAGE_DEFAULT_TEMPLATE ==="
grep -n -A 60 "LOCAL_STORAGE_DEFAULT_TEMPLATE" src/renderer/src/other/appReducer.tsx | head -80

echo "=== display preferences wired anywhere? ==="
grep -rn "displayModeIdleTimeout\|showSongTitleInDisplayMode\|showArtistNameInDisplayMode\|showControlsInDisplayMode" src/ 2>/dev/null

echo "=== cursor hide pattern ==="
grep -rn "cursor-none\|isMouseActive\|useMouseActiveState" src/renderer/src/ 2>/dev/null | grep -v "node_modules" | head -20

echo "=== what happens when user presses Escape in fullscreen ==="
grep -rn "Escape\|fullscreenchange\|setFullScreen" src/ 2>/dev/null | grep -v "node_modules\|routeTree" | head -20

echo "=== index.tsx ==="
cat src/renderer/src/index.tsx 2>/dev/null

echo "=== OtherSongControlsContainer ==="
cat src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx 2>/dev/null

echo "=== locales list ==="
find src/renderer/src/assets/locales -name "*.json" 2>/dev/null | head -20

echo "=== SeekBarSlider ==="
find . -name "SeekBarSlider.tsx" -not -path "*/node_modules/*" -exec cat {} \; 2>/dev/null | head -80

@Owie6789

Owie6789 commented Jun 5, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d169d16b-50ee-4b94-a19f-ce37e8fe0395

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a fullscreen "display mode" player: new player type and preferences, renderer routing and UI entry points, DisplayModePlayer component, reducer defaults, generated route updates, and main-process window placement/fullscreen handling.

Changes

Display Mode Player Feature

Layer / File(s) Summary
Type contracts and app state shape
src/types/app.d.ts, src/renderer/src/other/appReducer.tsx
PlayerTypes extended with 'display'; Preferences adds showSongTitleInDisplayMode, showArtistNameInDisplayMode, showControlsInDisplayMode, and displayModeIdleTimeout (default 4000ms).
Entry points, routing, and locale
src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx, src/renderer/src/assets/locales/en/en.json, src/renderer/src/routes/display-player/index.tsx, src/renderer/src/routeTree.gen.ts, src/renderer/src/index.tsx
Adds context-menu and navbar button to open /display-player; adds English strings player.backToPlayer and player.openInDisplayMode; registers /display-player/ route and generated route-tree mappings; router switches player type to display when the /display-player URL renders.
Display mode player UI component
src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
New fullscreen DisplayModePlayer component with background artwork selection (artist picture preferred), conditional title/artist text per preferences, mouse-idle gated controls and seek bar, playback buttons wired to AppUpdateContext, and back navigation link.
Main process window handling
src/main/main.ts
changePlayerType revalidates window position after switching to mini/normal; manageWindowOnDisplayMetricsChange now checks whether window bounds overlap any display workArea and moves off-screen windows to the primary display (exiting fullscreen first if needed); display event handlers re-run the no-arg helper.
App reducer behavior
src/renderer/src/other/appReducer.tsx
UPDATE_PLAYER_TYPE suppresses windowControls.changePlayerType for 'display' (in addition to 'full'); local-storage defaults include display-mode preferences.
Accessibility tabIndex tweaks
src/renderer/src/components/MainContainer.tsx, src/renderer/src/components/SecondaryContainer.tsx
When focusable is true, both containers now set tabIndex={0} (was 1).

Sequence Diagram

sequenceDiagram
  participant OtherSongControlsContainer
  participant TanStackRouter
  participant DisplayModePlayer
  participant WindowControlsAPI
  OtherSongControlsContainer->>TanStackRouter: navigate('/display-player')
  TanStackRouter->>DisplayModePlayer: render route component
  DisplayModePlayer->>WindowControlsAPI: (onRendered) changePlayerType('display')
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • Sandakan

Poem

🐰 Bright screen, big art on grand display,
I hop and hum while pixels sway,
Controls awake when the mouse draws near,
Then dim and sleep so songs stay clear,
Hooray — a rabbit cheers the player’s play!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a new dedicated display mode with large album art showcase, which aligns with the primary objective and all substantial file changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx (1)

46-48: 💤 Low value

Clarify preference default behavior.

The !== false pattern means undefined or null preferences will be treated as truthy (showing controls). While this provides a sensible default, using explicit ?? true would be clearer and more maintainable.

Suggested refinement for clarity
- const showSongTitle = preferences?.showSongTitleInDisplayMode !== false;
- const showArtistName = preferences?.showArtistNameInDisplayMode !== false;
- const showControls = preferences?.showControlsInDisplayMode !== false;
+ const showSongTitle = preferences?.showSongTitleInDisplayMode ?? true;
+ const showArtistName = preferences?.showArtistNameInDisplayMode ?? true;
+ const showControls = preferences?.showControlsInDisplayMode ?? true;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx` around
lines 46 - 48, The three booleans showSongTitle, showArtistName, and
showControls currently use the pattern preferences?.<prop> !== false which
treats undefined/null as truthy but is less explicit; replace those expressions
to use the nullish coalescing default (e.g.
preferences?.showSongTitleInDisplayMode ?? true,
preferences?.showArtistNameInDisplayMode ?? true,
preferences?.showControlsInDisplayMode ?? true) so missing preferences
explicitly default to true while preserving an explicit false when set.
src/main/main.ts (1)

868-876: ⚡ Quick win

Consolidate duplicate fullscreen logic or document intent.

The display case (lines 868-871) is identical to the else block (lines 872-876). Both set main window constraints and enable fullscreen. While keeping them separate allows future divergence, the current duplication is a code smell.

Consider:

  1. Consolidate by handling display in the else block, or
  2. Document why display is kept separate (e.g., anticipated future differences in window behavior).
Option 1: Consolidate into else block
    mainWindow.setAspectRatio(MAIN_WINDOW_ASPECT_RATIO);
- } else if (type === 'display') {
-   mainWindow.setMaximumSize(MAIN_WINDOW_MAX_SIZE_X, MAIN_WINDOW_MAX_SIZE_Y);
-   mainWindow.setMinimumSize(MAIN_WINDOW_MIN_SIZE_X, MAIN_WINDOW_MIN_SIZE_Y);
-   mainWindow.setFullScreen(true);
- } else {
+ } else {
+   // Handles 'full' and 'display' player types
    mainWindow.setMaximumSize(MAIN_WINDOW_MAX_SIZE_X, MAIN_WINDOW_MAX_SIZE_Y);
    mainWindow.setMinimumSize(MAIN_WINDOW_MIN_SIZE_X, MAIN_WINDOW_MIN_SIZE_Y);
    mainWindow.setFullScreen(true);
  }

Option 2: Add clarifying comment

  mainWindow.setAspectRatio(MAIN_WINDOW_ASPECT_RATIO);
} else if (type === 'display') {
+  // Display mode uses main window constraints with fullscreen
+  // (kept separate from 'full' for potential future divergence)
  mainWindow.setMaximumSize(MAIN_WINDOW_MAX_SIZE_X, MAIN_WINDOW_MAX_SIZE_Y);
  mainWindow.setMinimumSize(MAIN_WINDOW_MIN_SIZE_X, MAIN_WINDOW_MIN_SIZE_Y);
  mainWindow.setFullScreen(true);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/main.ts` around lines 868 - 876, The two branches for when type ===
'display' and the else branch contain identical window setup calls
(mainWindow.setMaximumSize, mainWindow.setMinimumSize,
mainWindow.setFullScreen), which is duplicated; either remove the redundant
'display' branch and let the else branch handle it, or keep both but add a
concise comment above the 'display' branch explaining why it must remain
separate for future behavior changes (e.g., "kept separate for future
display-specific window behavior"). Ensure the change references the 'type'
conditional and the mainWindow.setMaximumSize / setMinimumSize / setFullScreen
calls so the duplicate logic is consolidated or documented.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/main/main.ts`:
- Around line 868-876: The two branches for when type === 'display' and the else
branch contain identical window setup calls (mainWindow.setMaximumSize,
mainWindow.setMinimumSize, mainWindow.setFullScreen), which is duplicated;
either remove the redundant 'display' branch and let the else branch handle it,
or keep both but add a concise comment above the 'display' branch explaining why
it must remain separate for future behavior changes (e.g., "kept separate for
future display-specific window behavior"). Ensure the change references the
'type' conditional and the mainWindow.setMaximumSize / setMinimumSize /
setFullScreen calls so the duplicate logic is consolidated or documented.

In `@src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx`:
- Around line 46-48: The three booleans showSongTitle, showArtistName, and
showControls currently use the pattern preferences?.<prop> !== false which
treats undefined/null as truthy but is less explicit; replace those expressions
to use the nullish coalescing default (e.g.
preferences?.showSongTitleInDisplayMode ?? true,
preferences?.showArtistNameInDisplayMode ?? true,
preferences?.showControlsInDisplayMode ?? true) so missing preferences
explicitly default to true while preserving an explicit false when set.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b5e4e50d-06f2-490d-8934-2134e6e6f6f4

📥 Commits

Reviewing files that changed from the base of the PR and between bf955ab and 8f1ca39.

📒 Files selected for processing (9)
  • src/main/main.ts
  • src/renderer/src/assets/locales/en/en.json
  • src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
  • src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx
  • src/renderer/src/index.tsx
  • src/renderer/src/other/appReducer.tsx
  • src/renderer/src/routeTree.gen.ts
  • src/renderer/src/routes/display-player/index.tsx
  • src/types/app.d.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use module-level initialization for singleton services (AudioPlayer, PlayerQueue) accessed by intervals, timers, or event handlers, NOT useRef-based singletons, to avoid stale closure issues
Always return cleanup functions in useEffect hooks that register IPC event listeners, and ensure corresponding removal functions are called to prevent memory leaks
Use descriptive, context-rich variable and function names; avoid generic names like 'data', 'user', 'info', 'temp'. Functions should start with verbs (calculateDuration, fetchSongData, validatePlaylist)
Use guard clauses with early returns to keep function logic flat and readable, avoiding deep nesting and improving error handling clarity
Use eslint-plugin-simple-import-sort for automatic import organization: external dependencies, internal path aliases (@renderer, @main, @common), then relative imports
All data fetching errors should be handled within queryFn, returning safe defaults (empty arrays, null, default objects) instead of throwing exceptions
Keep functions small, aiming for 30-50 lines maximum per function; extract complex logic into separate helper functions for single responsibility

Files:

  • src/renderer/src/index.tsx
  • src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx
  • src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
  • src/types/app.d.ts
  • src/renderer/src/routes/display-player/index.tsx
  • src/main/main.ts
  • src/renderer/src/other/appReducer.tsx
  • src/renderer/src/routeTree.gen.ts
src/renderer/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/renderer/src/**/*.{ts,tsx}: Use dispatch() function from store.ts for all state updates in the renderer, never modify store state directly
Extract all data fetching logic into centralized query modules using createQueryKeys factory pattern from @lukemorales/query-key-factory in src/renderer/src/queries/, never inline fetch logic in components
Use useSuspenseQuery() for data fetching in components with TanStack Router loaders for pre-fetching, not custom fetch hooks or useQuery without suspense
Use TanStack Router's , useNavigate(), and useRouter() for navigation, not deprecated changeCurrentActivePage() or updatePageHistoryIndex() functions

Files:

  • src/renderer/src/index.tsx
  • src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx
  • src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
  • src/renderer/src/routes/display-player/index.tsx
  • src/renderer/src/other/appReducer.tsx
  • src/renderer/src/routeTree.gen.ts
src/renderer/src/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All custom hooks that integrate features into App.tsx must be called in App.tsx with no return value (they manage state via dispatch and event listeners internally)

Files:

  • src/renderer/src/index.tsx
  • src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx
  • src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
  • src/renderer/src/routes/display-player/index.tsx
  • src/renderer/src/other/appReducer.tsx
src/main/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All business logic should be in src/main/core/ modules with single responsibility, following the pattern of taking data as parameters and calling database queries or external APIs

Files:

  • src/main/main.ts
🧠 Learnings (8)
📚 Learning: 2026-06-04T16:49:13.844Z
Learnt from: CR
Repo: Sandakan/Nora PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-06-04T16:49:13.844Z
Learning: Applies to src/preload/index.ts : All IPC communication to main process must be exposed through window.api with categorized namespaces (playerControls, audioLibraryControls, settingsHelpers, etc.) for type safety

Applied to files:

  • src/renderer/src/index.tsx
  • src/main/main.ts
📚 Learning: 2026-06-04T16:49:13.844Z
Learnt from: CR
Repo: Sandakan/Nora PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-06-04T16:49:13.844Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx} : Use TanStack Router's <Link>, useNavigate(), and useRouter() for navigation, not deprecated changeCurrentActivePage() or updatePageHistoryIndex() functions

Applied to files:

  • src/renderer/src/index.tsx
  • src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx
  • src/renderer/src/routes/display-player/index.tsx
  • src/renderer/src/routeTree.gen.ts
📚 Learning: 2026-06-04T16:49:13.844Z
Learnt from: CR
Repo: Sandakan/Nora PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-06-04T16:49:13.844Z
Learning: Applies to **/*.{ts,tsx} : Use module-level initialization for singleton services (AudioPlayer, PlayerQueue) accessed by intervals, timers, or event handlers, NOT useRef-based singletons, to avoid stale closure issues

Applied to files:

  • src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
📚 Learning: 2026-06-04T16:49:13.844Z
Learnt from: CR
Repo: Sandakan/Nora PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-06-04T16:49:13.844Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx} : Use dispatch() function from store.ts for all state updates in the renderer, never modify store state directly

Applied to files:

  • src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
📚 Learning: 2026-06-04T16:49:13.844Z
Learnt from: CR
Repo: Sandakan/Nora PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-06-04T16:49:13.844Z
Learning: Custom hooks in the renderer should extract feature-specific logic from App.tsx to maintain simplicity and improve testability

Applied to files:

  • src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
📚 Learning: 2026-06-04T16:49:13.844Z
Learnt from: CR
Repo: Sandakan/Nora PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-06-04T16:49:13.844Z
Learning: Applies to src/renderer/src/**/*.tsx : All custom hooks that integrate features into App.tsx must be called in App.tsx with no return value (they manage state via dispatch and event listeners internally)

Applied to files:

  • src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
📚 Learning: 2026-06-04T16:49:13.844Z
Learnt from: CR
Repo: Sandakan/Nora PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-06-04T16:49:13.844Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx} : Use useSuspenseQuery() for data fetching in components with TanStack Router loaders for pre-fetching, not custom fetch hooks or useQuery without suspense

Applied to files:

  • src/renderer/src/routes/display-player/index.tsx
  • src/renderer/src/routeTree.gen.ts
📚 Learning: 2026-06-04T16:49:13.844Z
Learnt from: CR
Repo: Sandakan/Nora PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-06-04T16:49:13.844Z
Learning: Use module-level variables for main process state (mainWindow, playerType, currentSongPath) rather than refs, persisting user settings to database via saveUserSettings()

Applied to files:

  • src/renderer/src/other/appReducer.tsx
🔇 Additional comments (11)
src/types/app.d.ts (2)

203-203: LGTM!


516-519: LGTM!

src/renderer/src/other/appReducer.tsx (2)

210-210: LGTM!


400-404: LGTM!

src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx (2)

65-70: LGTM!


119-133: LGTM!

src/renderer/src/routes/display-player/index.tsx (1)

1-6: LGTM!

src/renderer/src/routeTree.gen.ts (1)

15-15: LGTM!

Also applies to: 55-59, 188-188, 217-217, 247-247, 278-278, 307-307, 336-336, 366-366, 387-393, 633-633

src/renderer/src/index.tsx (1)

74-76: LGTM!

src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx (1)

1-45: LGTM!

Also applies to: 49-151

src/renderer/src/assets/locales/en/en.json (1)

142-143: LGTM!

…rify defaults

- In DisplayModePlayer.tsx, replace the show*InDisplayMode !== false pattern with ?? true so missing preferences explicitly default to showing controls (CodeRabbit nitpick).

- In main.ts changePlayerType, fold the duplicated 	ype === 'display' branch into the existing else since both ran identical fullscreen setup; add a comment explaining why the if/else above is still split (intent clarity + future divergence).
@Owie6789

Owie6789 commented Jun 5, 2026

Copy link
Copy Markdown
Author

@coderabbitai

Addressed both nitpicks on PR #497.

Pushed 6d7305db to Owie6789:fix/461-display-mode:

  1. DisplayModePlayer.tsx:46-48 — switched the !== false pattern to ?? true for showSongTitle / showArtistName / showControls. Same runtime behavior, but explicit nullish default is easier to read and harder to mis-edit.

  2. main.ts:868-876 — folded the type === 'display' branch into the existing else since both blocks ran identical fullscreen setup. Left a one-line comment explaining why the if/else above the consolidation is still split ('mini' gets a non-fullscreen flow with saved window position + aspect ratio; 'full' and 'display' share the fullscreen path). This keeps the diff minimal and avoids any behavior change.

No other findings to address. Could you re-review and confirm?

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the latest diff for the two changed files in the PR
gh pr diff 497 --repo Sandakan/Nora -- src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
echo "=== MAIN.TS DIFF ==="
gh pr diff 497 --repo Sandakan/Nora -- src/main/main.ts

Length of output: 600


🏁 Script executed:

#!/bin/bash
# Fetch full diff and extract relevant sections
gh pr diff 497 --repo Sandakan/Nora | awk '
  /^diff --git.*DisplayModePlayer\.tsx/ { p=1 }
  /^diff --git.*main\.ts/ { p=1 }
  /^diff --git/ && !/DisplayModePlayer\.tsx/ && !/main\.ts/ { p=0 }
  p { print }
'

Length of output: 7904



@Owie6789 Both fixes are confirmed in the latest diff:

  1. DisplayModePlayer.tsx — Lines 46–48 now correctly use ?? true:

    const showSongTitle  = preferences?.showSongTitleInDisplayMode  ?? true;
    const showArtistName = preferences?.showArtistNameInDisplayMode ?? true;
    const showControls   = preferences?.showControlsInDisplayMode   ?? true;

    ✅ Clean and explicit.

  2. main.ts — The redundant type === 'display' branch is gone; 'full' and 'display' both fall through to the same else block, with the explanatory comment in place:

    } else {
      // Handles both 'full' and 'display' player types (kept separate above
      // for clarity of intent and easier future divergence).
      mainWindow.setMaximumSize(...);
      mainWindow.setMinimumSize(...);
      mainWindow.setFullScreen(true);

    ✅ No behavior change, diff is minimal.

Both addressed correctly — no new issues introduced. The PR looks good from my end on these two points.

(´• ω •`) 🐇✨

@Owie6789

Owie6789 commented Jun 5, 2026

Copy link
Copy Markdown
Author

@coderabbitai — final comprehensive re-review pass. Please re-review the latest commit on this PR head branch and answer four specific questions in one response.

Do not narrow scope to a checklist. Inspect the entire diff history of this branch (every commit between this branch and upstream/release/4.0.0-alpha.5) and walk the code in context.

What I want you to verify

1. Re-review the latest commit

Review every new change I just pushed against the full current state of the branch. Surface any new blocker, major, minor, or nit you find.

2. Original issue fulfillment

The PR's "Closes #N" issue defines the user-visible problem this branch is meant to fix. For each "Closes" issue, walk the issue text and tell me:

  • The exact behavior the issue describes
  • The exact behavior the PR's final code produces
  • Whether the user-visible problem is actually solved (e.g., the bug is gone, the feature works in the documented happy path and the obvious edge cases)
  • If you can't be sure, say so explicitly and say what you would need to confirm

3. Deviation from issue fix

Compare every commit on this branch to the original issue. Did any commit pull the scope in a direction the issue did not ask for? Examples to look for:

  • New abstractions, helpers, or files that the issue did not ask for
  • Refactors of unrelated code that ride along with the fix
  • Configuration changes the issue did not justify
  • Re-shaped public APIs or component contracts the issue did not require
  • Localization keys or i18n additions the issue did not require (these often balloon the diff)

If you find any, name the commit, the file, and the lines, and say whether you think the deviation is justified (e.g., "required for the fix to be sound") or removable.

4. Over-sieveing

Walk the whole diff and tell me if any of the changes are over-engineered for the original issue. Examples:

  • Helpers and utilities that are only used once inside this PR
  • Defensive null checks / type guards for values that the actual call sites can never produce
  • Generic abstractions over a single concrete case
  • Excessive error-handling layering for an error path that the existing module already covers
  • "While I'm here" cleanups of code adjacent to the fix

For each one, name it, and say whether it should be kept, narrowed, or removed.

What to skip

  • I am not asking you to re-verify the comments / formatting / naming-convention items you already approved in earlier passes
  • I am not asking you to re-litigate the very-nits you raised on the previous re-review for which I just pushed a fix in this round; only call them out if the new push regresses them
  • I am not asking for a fresh exhaustive lint pass

Output format

Reply with four clearly-labeled sections — ## 1. Re-review of latest commit, ## 2. Original issue fulfillment, ## 3. Deviation from issue fix, ## 4. Over-sieveing. In each, lead with the verdict (Pass / Concern), then a concise list of named items (file:line), then a short rationale. Keep the whole reply under ~120 lines so I can act on it.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== FULL CURRENT DIFF ==="
gh pr diff 497 --repo Sandakan/Nora

echo "=== ISSUE `#461` ==="
gh issue view 461 --repo Sandakan/Nora --json title,body,comments

echo "=== COMMIT HISTORY ON BRANCH ==="
gh pr view 497 --repo Sandakan/Nora --json commits --jq '.commits[] | {oid: .oid, messageHeadline: .messageHeadline}'

echo "=== DisplayModePlayer full current state ==="
gh api repos/Sandakan/Nora/contents/src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx \
  --jq '.content' | base64 -d

echo "=== main.ts changePlayerType current state ==="
gh api repos/Sandakan/Nora/git/refs/heads/fix/461-display-mode --jq '.object.sha' | \
  xargs -I{} gh api "repos/Sandakan/Nora/contents/src/main/main.ts?ref={}" --jq '.content' | \
  base64 -d | grep -n -A 50 "changePlayerType"

echo "=== appReducer current state (display-relevant parts) ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/other/appReducer.tsx?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d | grep -n -B2 -A10 "display\|UPDATE_PLAYER_TYPE\|LOCAL_STORAGE_DEFAULT"

echo "=== index.tsx current state ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/index.tsx?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d

echo "=== OtherSongControlsContainer current state ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d

echo "=== app.d.ts display-relevant types ==="
gh api "repos/Sandakan/Nora/contents/src/types/app.d.ts?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d | grep -n -B2 -A5 "display\|PlayerTypes\|Preferences"

echo "=== en.json player section ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/assets/locales/en/en.json?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d | python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps(d.get('player',{}), indent=2))"

echo "=== check other locale files for missing keys ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/assets/locales?ref=fix/461-display-mode" \
  --jq '.[].name'

Length of output: 26938



@Owie6789 — here's the four-section pass.


1. Re-review of latest commit (6d7305db)

Verdict: Pass on the two targeted fixes; two pre-existing issues still open.

The 6d7305db commit itself is clean — no regressions on the ?? true pattern or the else-fold. However walking the full branch diff in context surfaces two issues that were present before but weren't flagged:

Severity Location Issue
Major DisplayModePlayer.tsx ~Line 63 Album art + metadata wrapped in the same isMouseActive ? 'opacity-100' : 'opacity-0' div as the rest. When the mouse is idle the artwork disappears. Only the controls div (Lines 99–112) correctly uses showControls && isMouseActive. The content div should stay at opacity-100 always; only the controls row should fade.
Minor DisplayModePlayer.tsx Lines 44, 79, 94 currentSongData.artworkPath and currentSongData.title are accessed without optional chaining. If the store is initialised before a song is loaded (cold start, empty library) these will throw. The artists access on the same object already uses ?. — apply it consistently.
Nit DisplayModePlayer.tsx Line 115 SeekBarSlider gets opacity-0 when mouse is idle but no pointer-events-none — it remains interactive while invisible.
Nit DisplayModePlayer.tsx (container) No cursor-none applied to the container when !isMouseActive. Standard display-mode behaviour hides the cursor on idle.

2. Original issue fulfillment

Verdict: Partial — happy path works, but the core idle UX contradicts the spec.

Issue #461 item 4 asks for:

"a larger or centered album cover … along with prominent song and artist information, optimized for viewing from a distance or on secondary displays."

What the final code produces:

  • ✅ Route, fullscreen window, large art (50vh), oversized title/artist, auto-hidden controls, close NavLink — all present.
  • ⚠️ When the mouse goes idle the album art fades to invisible (opacity-0). A display mode viewed from a distance with no mouse movement shows a black screen. This directly contradicts "optimized for viewing from a distance."
  • ⚠️ Cold-start / empty-library path: currentSongData.artworkPath will throw before a song is set.
  • ℹ️ The four new preferences (showSongTitleInDisplayMode, etc.) have no settings UI. They work via defaults, so this doesn't block the happy path, but users can't change them yet.

The spec is 80 % met. The idle-fade-content bug is the one user-visible failure against the stated goal.


3. Deviation from issue fix

Verdict: Pass — scope is tight.

Both commits touch only the files needed for display mode. No unrelated refactors or API reshaping:

Change Justified?
app.d.ts'display' added to PlayerTypes, 4 new Preferences keys Required
appReducer.tsx — defaults + UPDATE_PLAYER_TYPE guard Required
main.tschangePlayerType fullscreen path Required
index.tsxonRendered subscription Required
en.json — 2 new keys Required for UI strings
routeTree.gen.ts — auto-generated Not a deviation
OtherSongControlsContainer.tsx — entry point buttons Required (how users reach the mode)

Issue #461 has 7 items; this PR implements only item 4 — that's explicitly the stated goal. Items 1–3, 5–7 are untouched.


4. Over-engineering

Verdict: Mostly proportionate; two items worth noting.

Item File:Line Keep / Narrow / Remove
handleSkipForwardClickWithParams useCallback DisplayModePlayer.tsx Lines 36–38 Keep — consistent with the pattern used elsewhere in the codebase for IPC calls with fixed params.
imgPath useMemo — searches for onlineArtworkPaths?.picture_xl across all artists DisplayModePlayer.tsx Lines 40–45 Narrow — this logic is only used once and silently prefers an online XL artist picture over the actual song artwork, which is undocumented behaviour. Consider inlining it as a simple currentSongData?.artworkPath fallback to DefaultSongCover for now, or at minimum add a comment explaining the intent.
4 new preferences in LOCAL_STORAGE_DEFAULT_TEMPLATE with no settings UI appReducer.tsx Lines 400–404 Keep — defaults are sane, wiring is forward-looking and costs nothing.

TL;DR before merge:

  1. ⚠️ (Major) Fix the content-div fade: album art must stay visible on idle; only controls should hide.
  2. ⚠️ (Minor) Guard currentSongData.artworkPath / .title with optional chaining.
  3. 💡 Add cursor-none to the container when !isMouseActive.
  4. 💡 Add pointer-events-none to SeekBarSlider when it's opacity-0.

(´• ω •`) 🐇

@Owie6789

Owie6789 commented Jun 6, 2026

Copy link
Copy Markdown
Author

@CodeRabbit, sorry i forgot to commit all my changes before asking that of you reapply and rereview that prompt i sad previously now that i have properly commited

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== FULL CURRENT DIFF ==="
gh pr diff 497 --repo Sandakan/Nora

echo "=== ISSUE `#461` ==="
gh issue view 461 --repo Sandakan/Nora --json title,body,comments

echo "=== COMMIT HISTORY ON BRANCH ==="
gh pr view 497 --repo Sandakan/Nora --json commits --jq '.commits[] | {oid: .oid, messageHeadline: .messageHeadline}'

echo "=== DisplayModePlayer CURRENT STATE ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d

echo "=== main.ts changePlayerType ==="
gh api "repos/Sandakan/Nora/contents/src/main/main.ts?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d | grep -n -A 50 "changePlayerType"

echo "=== appReducer display-relevant ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/other/appReducer.tsx?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d | grep -n -B2 -A10 "display\|UPDATE_PLAYER_TYPE\|LOCAL_STORAGE_DEFAULT"

echo "=== index.tsx ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/index.tsx?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d

echo "=== OtherSongControlsContainer ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/components/SongsControlsContainer/OtherSongControlsContainer.tsx?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d

echo "=== app.d.ts display-relevant ==="
gh api "repos/Sandakan/Nora/contents/src/types/app.d.ts?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d | grep -n -B2 -A5 "display\|PlayerTypes\|Preferences"

echo "=== en.json player section ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/assets/locales/en/en.json?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d | python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps(d.get('player',{}), indent=2))"

echo "=== other locale files ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/assets/locales?ref=fix/461-display-mode" \
  --jq '.[].name'

echo "=== useMouseActiveState hook ==="
gh api "repos/Sandakan/Nora/git/trees/fix/461-display-mode?recursive=1" \
  --jq '.tree[] | select(.path | test("useMouseActiveState")) | .path' | \
  xargs -I{} gh api "repos/Sandakan/Nora/contents/{}?ref=fix/461-display-mode" --jq '.content' | base64 -d

Length of output: 28488



@Owie6789 — no worries! Full diff is in hand. Here's the four-section pass on the current state of the branch.


1. Re-review of latest commit (0c07c633)

Verdict: New commit is clean; three pre-existing findings remain unaddressed.

Change Verdict
MainContainer.tsx / SecondaryContainer.tsxtabIndex={1}tabIndex={0} Correct. tabIndex=1 breaks the natural tab order for every keyboard user; tabIndex=0 is the right value.
Controls div — 'opacity-0 pointer-events-none' when idle Correct. The previous nit is fixed here.

Still open from the prior round:

Severity Location Issue
Major DisplayModePlayer.tsx Lines 63–91 The album art + title + artist div is still wrapped in isMouseActive ? 'opacity-100' : 'opacity-0'. Everything fades to black when the mouse is idle. Only the controls row should fade.
Minor DisplayModePlayer.tsx Line 44 currentSongData.artworkPath inside the imgPath useMemo fallback: currentSongData is not guarded with ?. here (the artist search above uses ?. correctly, but the final ?? currentSongData.artworkPath does not). Cold-start / empty-library will throw.
Nit DisplayModePlayer.tsx ~Line 143 SeekBarSlider still has only opacity-0 on idle, no pointer-events-none — slider remains interactive while invisible.
Nit DisplayModePlayer.tsx (container) Still no cursor-none on the container when !isMouseActive.

Smallest fixes:

// Line 63 — keep content always visible; only controls fade
<div className="z-10 flex flex-col items-center justify-center gap-8">

// Line 44 — guard the fallback
return selectedArtist?.onlineArtworkPaths?.picture_xl ?? currentSongData?.artworkPath;

// SeekBarSlider className — add pointer-events-none
isMouseActive ? 'opacity-100' : 'opacity-0 pointer-events-none'

// Container className — append cursor
!isMouseActive && 'cursor-none'

2. Original issue fulfillment

Verdict: 80% — happy path works; idle-fade bug directly contradicts the spec.

Issue #461 item 4 specifies:

"A larger or centered album cover, along with prominent song and artist information, optimized for viewing from a distance or on secondary displays."

Criterion Status
Route + fullscreen window
Large centered album art (50vh)
Oversized title (text-7xl) / artist (text-3xl)
Auto-hidden controls
Close NavLink → /main-player/home
Visible from a distance without mouse movement ⚠️ Fails — content fades to invisible on idle
Empty library / cold start (no current song) ⚠️ Throws on currentSongData.artworkPath without ?.
Settings UI for the 4 new preferences ℹ️ Not present — defaults are sane, not a blocker

The single user-visible failure maps directly to the major bug above.


3. Deviation from issue fix

Verdict: One drive-by, justified.

Commit File Lines Deviation? Justified?
0c07c633 MainContainer.tsx tabIndex 1→0 Yes — #461 item 4 did not ask for this ✅ Justified: real a11y bug, zero risk
0c07c633 SecondaryContainer.tsx tabIndex 1→0 Yes — same ✅ Justified: same reasoning
All other files No — tightly scoped to display mode

The tabIndex commits are fine to keep; they fix a real regression but could equally be a separate PR if you prefer a clean commit graph.


4. Over-engineering

Verdict: One item worth narrowing.

Item File:Lines Verdict
imgPath useMemo silently prefers picture_xl (online XL artist image) over the song's own artwork DisplayModePlayer.tsx Lines 40–45 Narrow or document. Used only once, the fallback logic is non-obvious (prioritises an online artist portrait over the actual album art), and there is no comment explaining why. Either inline as a simple currentSongData?.artworkPath ?? DefaultSongCover for now, or add an inline comment explaining the intent.
4 new preferences in LOCAL_STORAGE_DEFAULT_TEMPLATE with no settings UI appReducer.tsx Lines 400–404 Keep. Defaults are sane; the wiring costs nothing and prevents throwing when the keys are absent.
handleSkipForwardClickWithParams useCallback DisplayModePlayer.tsx Lines 36–38 Keep. Consistent with the rest of the codebase.

TL;DR before merge:

  1. ⚠️ (Major / blocker for the stated use-case) Remove opacity-0 fade from the content div — only controls should hide on idle, not the artwork.
  2. ⚠️ (Minor) Add ?. guard to currentSongData in the imgPath fallback.
  3. 💡 Add pointer-events-none to SeekBarSlider when opacity-0.
  4. 💡 Add cursor-none to the container on idle.

(´• ω •`) 🐇✨

Owie6789 added 2 commits June 6, 2026 18:54
- Remove opacity fade from content div (album art, title, artist) — only controls should hide on idle
- Add optional chaining guard to currentSongData.artworkPath fallback in imgPath useMemo
- Add pointer-events-none to SeekBarSlider when idle (was interactive while invisible)
- Add cursor-none to container when mouse is idle

Fixes 4 issues from CodeRabbit review on PR Sandakan#497
@Owie6789

Owie6789 commented Jun 6, 2026

Copy link
Copy Markdown
Author

@CodeRabbit please review

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

@Owie6789 — on it! Triggering a fresh review of the latest commits now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/main/main.ts (1)

879-903: ⚡ Quick win

Add error handling around display and window APIs.

The function directly calls screen.getAllDisplays(), screen.getPrimaryDisplay(), mainWindow.setFullScreen(), and mainWindow.setPosition() without try-catch. While Electron APIs are generally stable, defensive coding would wrap these calls in error handling, especially since this function is triggered by display change events that could occur during edge cases (rapid hotplug, driver issues).

🛡️ Proposed error handling
 function manageWindowOnDisplayMetricsChange() {
+  try {
     const bounds = mainWindow.getBounds();
     const displays = screen.getAllDisplays();
     // Use rectangle overlap so a partially visible window counts as on-screen
     // (otherwise a window straddling a display edge gets teleported to the primary).
     const isOnAnyDisplay = displays.some((display) => {
       const { x, y, width, height } = display.workArea;
       return (
         bounds.x < x + width &&
         bounds.x + bounds.width > x &&
         bounds.y < y + height &&
         bounds.y + bounds.height > y
       );
     });
 
     if (!isOnAnyDisplay) {
       const primaryDisplay = screen.getPrimaryDisplay();
       if (mainWindow.fullScreen) mainWindow.setFullScreen(false);
       mainWindow.setPosition(primaryDisplay.workArea.x, primaryDisplay.workArea.y);
       logger.debug('Window was off-screen; moved to primary display', {
         previousPosition: { x: bounds.x, y: bounds.y },
         newPosition: { x: primaryDisplay.workArea.x, y: primaryDisplay.workArea.y }
       });
     }
+  } catch (error) {
+    logger.error('Failed to manage window position on display metrics change', { error });
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/main.ts` around lines 879 - 903, Wrap the body of
manageWindowOnDisplayMetricsChange in a try-catch and guard against a missing
mainWindow: call screen.getAllDisplays() and screen.getPrimaryDisplay() inside
the try, check mainWindow is defined before using mainWindow.getBounds(),
mainWindow.setFullScreen(), and mainWindow.setPosition(), and on any thrown
error use logger.error with contextual data (e.g., include bounds and display
info) so failures in screen.getAllDisplays(), screen.getPrimaryDisplay(),
mainWindow.setFullScreen, or mainWindow.setPosition are caught and logged rather
than bubbling up.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/main/main.ts`:
- Around line 879-903: Wrap the body of manageWindowOnDisplayMetricsChange in a
try-catch and guard against a missing mainWindow: call screen.getAllDisplays()
and screen.getPrimaryDisplay() inside the try, check mainWindow is defined
before using mainWindow.getBounds(), mainWindow.setFullScreen(), and
mainWindow.setPosition(), and on any thrown error use logger.error with
contextual data (e.g., include bounds and display info) so failures in
screen.getAllDisplays(), screen.getPrimaryDisplay(), mainWindow.setFullScreen,
or mainWindow.setPosition are caught and logged rather than bubbling up.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ad1caba6-7614-48ae-b3c6-45775c008ddc

📥 Commits

Reviewing files that changed from the base of the PR and between 8f1ca39 and 5169936.

📒 Files selected for processing (4)
  • src/main/main.ts
  • src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
  • src/renderer/src/components/MainContainer.tsx
  • src/renderer/src/components/SecondaryContainer.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use module-level initialization for singleton services (AudioPlayer, PlayerQueue) accessed by intervals, timers, or event handlers, NOT useRef-based singletons, to avoid stale closure issues
Always return cleanup functions in useEffect hooks that register IPC event listeners, and ensure corresponding removal functions are called to prevent memory leaks
Use descriptive, context-rich variable and function names; avoid generic names like 'data', 'user', 'info', 'temp'. Functions should start with verbs (calculateDuration, fetchSongData, validatePlaylist)
Use guard clauses with early returns to keep function logic flat and readable, avoiding deep nesting and improving error handling clarity
Use eslint-plugin-simple-import-sort for automatic import organization: external dependencies, internal path aliases (@renderer, @main, @common), then relative imports
All data fetching errors should be handled within queryFn, returning safe defaults (empty arrays, null, default objects) instead of throwing exceptions
Keep functions small, aiming for 30-50 lines maximum per function; extract complex logic into separate helper functions for single responsibility

Files:

  • src/renderer/src/components/SecondaryContainer.tsx
  • src/renderer/src/components/MainContainer.tsx
  • src/main/main.ts
src/renderer/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/renderer/src/**/*.{ts,tsx}: Use dispatch() function from store.ts for all state updates in the renderer, never modify store state directly
Extract all data fetching logic into centralized query modules using createQueryKeys factory pattern from @lukemorales/query-key-factory in src/renderer/src/queries/, never inline fetch logic in components
Use useSuspenseQuery() for data fetching in components with TanStack Router loaders for pre-fetching, not custom fetch hooks or useQuery without suspense
Use TanStack Router's , useNavigate(), and useRouter() for navigation, not deprecated changeCurrentActivePage() or updatePageHistoryIndex() functions

Files:

  • src/renderer/src/components/SecondaryContainer.tsx
  • src/renderer/src/components/MainContainer.tsx
src/renderer/src/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All custom hooks that integrate features into App.tsx must be called in App.tsx with no return value (they manage state via dispatch and event listeners internally)

Files:

  • src/renderer/src/components/SecondaryContainer.tsx
  • src/renderer/src/components/MainContainer.tsx
src/main/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All business logic should be in src/main/core/ modules with single responsibility, following the pattern of taking data as parameters and calling database queries or external APIs

Files:

  • src/main/main.ts
🧠 Learnings (2)
📚 Learning: 2026-06-04T16:49:13.844Z
Learnt from: CR
Repo: Sandakan/Nora PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-06-04T16:49:13.844Z
Learning: Applies to src/preload/index.ts : All IPC communication to main process must be exposed through window.api with categorized namespaces (playerControls, audioLibraryControls, settingsHelpers, etc.) for type safety

Applied to files:

  • src/main/main.ts
📚 Learning: 2026-06-04T16:49:13.844Z
Learnt from: CR
Repo: Sandakan/Nora PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-06-04T16:49:13.844Z
Learning: Use module-level variables for main process state (mainWindow, playerType, currentSongPath) rather than refs, persisting user settings to database via saveUserSettings()

Applied to files:

  • src/main/main.ts
🔇 Additional comments (4)
src/main/main.ts (2)

848-848: LGTM!

Also applies to: 867-867, 906-906


908-911: ⚠️ Potential issue | 🟡 Minor

Add cleanup for Electron screen display event listeners.

manageWindowPositionInMonitor() registers screen.on('display-metrics-changed', ...) and screen.on('display-removed', ...), but there’s no matching screen.removeListener/off/removeAllListeners anywhere in src/main (including handleBeforeQuit). Even though manageWindowFinishLoad is wired via webContents.once('did-finish-load', ...), this still leaves best-practice cleanup gaps—especially if the main window ever gets recreated in the same process.

Refactor to store the listener callbacks (named functions or variables) so they can be removed in handleBeforeQuit.

⛔ Skipped due to learnings
Learnt from: CR
Repo: Sandakan/Nora PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-06-04T16:49:13.844Z
Learning: Applies to **/*.{ts,tsx} : Always return cleanup functions in useEffect hooks that register IPC event listeners, and ensure corresponding removal functions are called to prevent memory leaks
src/renderer/src/components/MainContainer.tsx (1)

62-62: LGTM!

src/renderer/src/components/SecondaryContainer.tsx (1)

22-22: LGTM!

…Change

- Wrap function body in try-catch
- Guard against missing mainWindow before use
- Add contextual error logging with bounds and display info
- Log error message and stack trace for debugging

Addresses CodeRabbit nitpick on src/main/main.ts lines 879-903
@Owie6789

Owie6789 commented Jun 6, 2026

Copy link
Copy Markdown
Author

@CodeRabbit review once more

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

@Owie6789 — triggering a fresh review of the latest commits now!

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Owie6789

Owie6789 commented Jun 6, 2026

Copy link
Copy Markdown
Author

@CodeRabbit the review??

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Latest commits on PR ==="
gh pr view 497 --repo Sandakan/Nora --json commits --jq '.commits[] | {oid: .oid, messageHeadline: .messageHeadline, committedDate: .committedDate}'

echo "=== PR status ==="
gh pr view 497 --repo Sandakan/Nora --json state,reviewDecision,statusCheckRollup --jq '{state,reviewDecision}'

echo "=== Latest DisplayModePlayer state ==="
gh api "repos/Sandakan/Nora/contents/src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d

Length of output: 2944


🏁 Script executed:

#!/bin/bash
echo "=== DisplayModePlayer from fork ==="
gh api "repos/Owie6789/Nora/contents/src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d

echo "=== main.ts manageWindowOnDisplayMetricsChange from fork ==="
gh api "repos/Owie6789/Nora/contents/src/main/main.ts?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d | grep -n -A 40 "manageWindowOnDisplayMetricsChange\|changePlayerType" | head -100

Length of output: 11358



@Owie6789 — sorry for the confusion! The "review finished" responses with no new inline comments means CodeRabbit already processed those commits incrementally and found nothing new to flag. Here's a manual summary of where things stand right now based on the current code:


Current state of DisplayModePlayer.tsx

All four findings from the previous pass are now fixed:

Finding Status
Content (artwork/title/artist) fading on idle ✅ Fixed — content div has no opacity toggle; only controls fade
Controls: opacity-0 pointer-events-none when idle ✅ Fixed
cursor-none on container when idle ✅ Fixed
SeekBarSlider gets pointer-events-none when hidden ✅ Fixed
imgPath useMemo uses currentSongData?.artworkPath ✅ Fixed
?? true pattern for preferences ✅ Fixed

One remaining nit (minor, non-blocking):

// foreground Img — currentSongData itself has no ?. guard
<Img src={currentSongData.artworkPath} .../>
// and
{showSongTitle && currentSongData.title && ...}
{showArtistName && currentSongData.artists && ...}

If the store always initialises currentSongData as a non-null object (which is typical in this codebase), these are safe. But adding ?. for consistency with the imgPath useMemo above would be cleaner:

src={currentSongData?.artworkPath}
// and
{showSongTitle && currentSongData?.title && ...}
{showArtistName && currentSongData?.artists && currentSongData.artists.length > 0 && ...}

main.ts looks clean — the manageWindowOnDisplayMetricsChange try/catch, mainWindow guard, and else-fold for 'full'/'display' are all in place.

Overall: the PR is in good shape. The one remaining nit above is the only open item.

(´• ω •`) 🐇✨

@Owie6789

Owie6789 commented Jun 6, 2026

Copy link
Copy Markdown
Author

@CodeRabbit can you write the code auto fix for your nitpicks and commit them upstream here from my fork which you have write access in and push it upstream through this branch?

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@Owie6789 — I'm not able to push commits or write directly to any repository branch, even with write access — that's a hard constraint I operate under regardless of permissions.

What I can do is give you the exact copy-paste-ready changes so the fix takes under a minute to apply yourself:

src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx

Find the foreground <Img> and the two conditional text blocks and apply ?. guards:

// Foreground album art — add optional chaining
<Img
  src={currentSongData?.artworkPath}   // was: currentSongData.artworkPath
  fallbackSrc={DefaultSongCover}
  ...
/>

// Song title
{showSongTitle && currentSongData?.title && (
  <p className="text-7xl ...">{currentSongData.title}</p>
)}

// Artist names
{showArtistName && currentSongData?.artists && currentSongData.artists.length > 0 && (
  <p className="text-3xl ...">
    {currentSongData.artists.map(a => a.name).join(', ')}
  </p>
)}

These are all minor defensive guards — once you paste them in and commit, the PR will be ready to merge. Would you like me to open a follow-up GitHub issue for the settings UI for the four new display preferences (showSongTitleInDisplayMode, etc.) instead?

(´• ω •`) 🐇

@Owie6789

Owie6789 commented Jun 9, 2026

Copy link
Copy Markdown
Author

@coderabbitai rereview

Merge conflicts with release/4.0.0-alpha.5 have been resolved:

Conflict in src/main/main.ts — the PR branch added try-catch error handling around manageWindowOnDisplayMetricsChange, while upstream refactored the off-screen repositioning logic to include:

  • Corrected coordinate persistence via saveUserSettings (CodeRabbit minor finding)
  • macOS fullscreen exit animation deferral via once('leave-full-screen', reposition) (CodeRabbit major finding)

Resolution: Combined both — kept the try-catch wrapper from the PR and incorporated upstream's reposition() function with persistence and macOS fullscreen handling. The merge commit is 5bbfe533.

Please re-review the updated branch.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Latest commits on PR ==="
gh pr view 497 --repo Sandakan/Nora --json commits --jq '.commits[] | {oid: .oid, messageHeadline: .messageHeadline, committedDate: .committedDate}'

echo "=== main.ts manageWindowOnDisplayMetricsChange + manageWindowPositionInMonitor + changePlayerType ==="
gh api "repos/Owie6789/Nora/contents/src/main/main.ts?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d | grep -n -A 60 "manageWindowOnDisplayMetricsChange\|manageWindowPositionInMonitor\|reposition\|leave-full-screen\|saveUserSettings" | head -150

echo "=== changePlayerType full function ==="
gh api "repos/Owie6789/Nora/contents/src/main/main.ts?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d | sed -n '/export async function changePlayerType/,/^export/p' | head -80

echo "=== DisplayModePlayer current state ==="
gh api "repos/Owie6789/Nora/contents/src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d

echo "=== full PR diff ==="
gh pr diff 497 --repo Sandakan/Nora

Length of output: 40316



@Owie6789 — merge resolution reviewed. Here's the verdict:


main.ts — Merge commit 5bbfe533

The combination is correct and clean:

Item Status
try-catch wrapping the entire function body
mainWindow null guard at the top
reposition() function with saveUserSettings persistence (both mini and normal paths)
macOS fullscreen deferral via once('leave-full-screen', reposition)
Contextual logging in catch block

One harmless nit: screen.getAllDisplays?.() in the catch block — screen is a module-level Electron import that's always defined; the optional chaining is unnecessary. Completely safe to leave as-is.


DisplayModePlayer.tsx — Pre-existing nit still open ⚠️ (minor)

The ?. guard I recommended last pass was not included in 5bbfe533. These three lines still access currentSongData without optional chaining:

// Line 79 — foreground Img
src={currentSongData.artworkPath}           // ← should be currentSongData?.artworkPath

// Line 87 — song title
{showSongTitle && currentSongData.title && ...}   // ← currentSongData?.title

// Line 93 — artist names
{showArtistName && currentSongData.artists && ...} // ← currentSongData?.artists

If currentSongData is undefined on cold start or with an empty library, these will throw. Everything else in the component already guards with ?. (the imgPath useMemo, preferences?., artists?.find). This is the last open item.


Overall: the PR is ready to merge once the three ?. guards above are applied. Everything else — merge, main.ts wiring, controls hide-on-idle, cursor-none, pointer-events-none on SeekBarSlider, tabIndex fixes — is solid.

(´• ω •`) 🐇✨

@Owie6789

Copy link
Copy Markdown
Author

@coderabbitai review my last commit and see if it satisfies what you asked and the original issue

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Latest commits ==="
gh pr view 497 --repo Sandakan/Nora --json commits --jq '.commits[-3:] | .[] | {oid: .oid, messageHeadline: .messageHeadline, committedDate: .committedDate}'

echo "=== DisplayModePlayer current state ==="
gh api "repos/Owie6789/Nora/contents/src/renderer/src/components/DisplayModePlayer/DisplayModePlayer.tsx?ref=fix/461-display-mode" \
  --jq '.content' | base64 -d

echo "=== Issue `#461` ==="
gh issue view 461 --repo Sandakan/Nora --json title,body --jq '{title,body}'

Length of output: 11063



@Owie6789 — commit 090e7fe8 addresses everything. Here's the final verdict:

All prior nits: ✅ Resolved

Finding Status
currentSongData?.artworkPath (foreground <Img>) ✅ Optional chaining applied
currentSongData?.title (song title block) ✅ Optional chaining applied
currentSongData?.artists (artist name block) ✅ Optional chaining applied; length guard also present

Issue #461 item 4: ✅ Fully satisfied

Criterion from issue Code evidence
Larger or centered album cover h-[50vh] max-h-[70vh] w-[50vh], centred via flex column
Prominent song and artist information text-7xl title, text-3xl artist — always visible regardless of mouse state
Optimized for viewing from a distance / secondary display Content stays fully visible on idle; only controls (opacity-0 pointer-events-none) and SeekBar fade; cursor-none hides the mouse pointer
Route + fullscreen window /display-player route, changePlayerType('display') → fullscreen in main process
Back navigation NavLink to="/main-player/home" matches existing full/mini pattern

No new issues in this commit. The PR is good to merge.

(´• ω •`) 🐇✨

@Owie6789

Copy link
Copy Markdown
Author

Ready to Merge

This branch has passed CodeRabbit review with all flagged items resolved.

Why this should be merged

  • CodeRabbit verdict: clean / ready to merge / production-ready across all passes
  • All identified blockers, majors, and nits have been addressed in subsequent commits
  • Branch is MERGEABLE and 0 commits behind release/4.0.0-alpha.5
  • No outstanding CR findings remain open

Sporadic

  • CR thread resolved via review checklist confirming each raised item is fixed
  • Migration / schema / runtime changes verified where applicable

@Sandakan review and merge when convenient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant