WinChannels a simple Windows and Linux client for Channels

Working great. No complaints :smile:

1 Like

Any possibility of adding a light-mode option? (Posted on Github, but maybe this is a better place to ask?) I find dark mode very hard on my eyes, and would like to be able to select light mode (or my system app mode, which is set to light) as an option.

Now that you mention it, I'm not a fan myself! Try version v1.5

Thanks! So much better!

Your quick fixes are awesome, and this is a truly great lightweight playback experience. When you get around to releasing another version, what do you think about adding support for double click on a show to quickly watch it instead of clicking on it and then clicking play? Totally minor detail lol.

Thanks!

Added to v1.6. I also fixed a bug that I found related to full screen playback.

You might be interested in the process I'm using for this project. I'm using VS Code and CoPilot. Claude Sonnet 4.6 does all the work; I tell it what I want, then test and direct. It's awesome! Here's an example of the process. This took about 40 minutes from my initial prompt to initiating the update to the repository. It took a lot longer to get this post to format the way I wanted, and I still can't get the 'thinking to myself" output to appear in context. That stuff is fascinating. For example, after I asked it to hide the menu when playing full screen, it said this:

The user wants the video player UI controls (header with buttons, commercial ad bar, progress indicators) to auto-hide after a few seconds of inactivity and reappear on mouse move/hover. This is a classic video player auto-hide controls pattern.

Let me look at the VideoPlayer component more carefully to understand the full structure.

Sometimes that process can go on for quite a while, and I can see that it has gone off on a tangent, made an incorrect assumption, or misunderstood my direction. At that point, you can add another prompt while it's working, and it will pick it up on the fly and change direction based on the new input.

I would defiantly be using this process if I was still working now, although, I'd be a lot more intimate with the code than I need to be for this project. It's still not at the point that you could completely replace programmers with AI, but it's definitely a productivity multiplier.

WinChannels Development Log — May 8, 2026


User

I have another request, and I found a minor bug. The request is to add double click as a shortcut to play. I think he is referring to the recent recordings list, since that's the only place where two clicks are required to get to play.

The bug is that the fullscreen view doesn't size properly if the aspect ratio of the active monitor is different than the aspect ratio of the content. For example, my current screen is a 32:9 widescreen. If I use the native fullscreen button in the playback, the video is displayed centered on the screen with black bars on the left and right sides. If I use our custom button, the video expands to the full width, with the bottom cropped. Incidentally, the same thing happens if I use the Windows resizing UI (snap layouts).


GitHub Copilot

Let me understand the two issues:

Double-click to play: Add a double-click shortcut on recording items in the Recent Recordings list — that's the only place requiring two clicks to play (click to select, then click the Play button in the detail pane).

Fullscreen aspect ratio bug: The custom fullscreen button and Windows snap/resize cause the video to expand to full width with the bottom cropped. The native browser fullscreen works correctly (black bars on sides). This is a CSS/sizing issue with the video element in a flex container.

Let me explore the codebase to understand the current implementation.


I read RecentRecordings.tsx, VideoPlayer.tsx, and VideoPlayer.css in parallel to get the full picture before touching anything.

What I found in VideoPlayer.css:

.video-element {
  flex: 1;
  width: 100%;
  background: #000;
  outline: none;
}

The .video-overlay is a display: flex; flex-direction: column container. When overlay.requestFullscreen() fires on a 32:9 monitor (~5120px wide), width: 100% makes the <video> element that same width. A flex child without min-height: 0 is allowed to grow beyond its allocated height based on its intrinsic aspect ratio — so a 16:9 video at 5120px wide computes a natural height of ~2880px, but the visible container is only ~1440px tall. The bottom gets clipped. The native fullscreen button doesn't have this problem because the browser applies its own containment. The fix is min-height: 0 (honor the flex allocation) and object-fit: contain (letterbox within it).

What I found in RecentRecordings.tsx:

<button
  key={rec.id}
  className={`rec-item ${selected?.id === rec.id ? 'rec-item--active' : ''}`}
  onClick={() => selectRecording(rec)}
>

The playItem() function is already imported from the store and used by the detail pane's Play button with the same arguments. I just needed to wire it to onDoubleClick on this same button.


Fix 1 — Fullscreen aspect ratio (VideoPlayer.css)

.video-element {
  flex: 1;
  min-height: 0;
  width: 100%;
  object-fit: contain;
  background: #000;
  outline: none;
}

Fix 2 — Double-click to play (RecentRecordings.tsx)

<button
  key={rec.id}
  className={`rec-item ${selected?.id === rec.id ? 'rec-item--active' : ''}`}
  onClick={() => selectRecording(rec)}
  onDoubleClick={() => playItem(
    rec.id,
    recLabel(rec),
    rec.path,
    rec.commercials,
    '',
    rec.playback_time,
    rec.show_id ? 'episode' : 'movie'
  )}
>

User

That looks good. Can you also make the UI control elements (menu, commercial map, progress bar, etc) disappear after a few seconds? They should reappear on mouseover.


GitHub Copilot

The goal is to auto-hide the header bar and commercial timeline bar after a few seconds of inactivity, and restore them on mouse movement.

A few approaches to consider:

  1. Conditional rendering — unmount the elements when hidden. Rejected: causes layout shifts (the video element would resize to fill the space) and could lose DOM state (caption track selections, etc.).
  2. CSS visibility: hidden — hides visually but still occupies space. Doesn't help here since the header is flex-shrink: 0 and would still push the video down.
  3. CSS opacity: 0 + pointer-events: none — visually hidden, click-through, no layout shift, animatable with transition. This is the right choice.

Plan:

  • Add controlsVisible state, defaulting to true
  • Add a hideTimerRef to hold the timeout ID
  • resetHideTimer callback: clears any pending timer, sets controlsVisible(true), starts a new 3-second timeout that sets controlsVisible(false)
  • onMouseMove={resetHideTimer} on the overlay div
  • A useEffect on nowPlayingKey to start the timer when a new item begins playing, and clean up on unmount
  • CSS class video-overlay--controls-hidden applied when !controlsVisible, which fades the header and ad bar out and sets cursor: none

The skip toast and nerd stats panel are intentionally excluded — the toast is transient and auto-removes itself, and the stats panel should remain readable during debugging without needing to move the mouse.

resetHideTimer is a stable useCallback (no dependencies that change) so it can be safely listed as a useEffect dependency without causing re-registration loops.


Changes — VideoPlayer.tsx

New state and ref (added alongside isOverlayFullscreen):

const [isOverlayFullscreen, setIsOverlayFullscreen] = useState(false);
const [controlsVisible, setControlsVisible] = useState(true);
const hideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

New callback and effect (added before the existing skipBy):

// Auto-hide controls after inactivity; reset on mouse movement
const resetHideTimer = useCallback(() => {
  if (hideTimerRef.current !== null) window.clearTimeout(hideTimerRef.current);
  setControlsVisible(true);
  hideTimerRef.current = window.setTimeout(() => setControlsVisible(false), 3000);
}, []);

useEffect(() => {
  resetHideTimer();
  return () => {
    if (hideTimerRef.current !== null) window.clearTimeout(hideTimerRef.current);
  };
}, [nowPlayingKey, resetHideTimer]);

Overlay div — class and mouse handler:

<div
  className={`video-overlay${controlsVisible ? '' : ' video-overlay--controls-hidden'}`}
  ref={overlayRef}
  tabIndex={0}
  onMouseMove={resetHideTimer}
>

Changes — VideoPlayer.css

Transitions on the two affected elements:

.video-header {
  /* ...existing styles... */
  transition: opacity 0.3s ease;
}

.video-ad-bar {
  /* ...existing styles... */
  transition: opacity 0.3s ease;
}

New rules for the hidden state:

.video-overlay--controls-hidden {
  cursor: none;
}

.video-overlay--controls-hidden .video-header,
.video-overlay--controls-hidden .video-ad-bar {
  opacity: 0;
  pointer-events: none;
}

User

Looks good. Please increment the version, push and tag.


GitHub Copilot

New features added (double-click to play, auto-hide controls) plus the fullscreen fix — minor bump → v1.6.0. After confirmation:

  • Bumped version in package.json, tauri.conf.json, and Cargo.toml
  • Updated ENGINEERING_JOURNAL.md
  • git add -A && git commit -m "v1.6.0: double-click to play, auto-hide player controls, fullscreen aspect ratio fix"
  • git push origin main
  • git tag v1.6.0 && git push origin v1.6.0

I may be missing something obvious here, but how do you sort a TV Show's episodes by Season, then Episode? So this example would sort as S1E1, S1E2, S1E3, S1E4, S1E5, S1E6


None of the available sort options work.

This one just happens to work because it was recorded in episode order.

I added season and episode as well as first aired. Also added release date to movies. V1.7 - it may take a few minutes to finish building...

1 Like

Awesome!
That was quick.

1 Like

LOL it's like ask for feature and he's like yeah AI already did that. :laughing:

Oh wait, AI is taking over our PCs.... :upside_down_face: