Skip to content

moq-boy: Review+revamp JS player#1224

Merged
kixelated merged 2 commits into
mainfrom
moq-boy-fixes
Apr 7, 2026
Merged

moq-boy: Review+revamp JS player#1224
kixelated merged 2 commits into
mainfrom
moq-boy-fixes

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Summary

  • Split expanded view into left (controls) and right (stats) panels, capping video at 4x native res
  • Enter key now only triggers Game Boy start button, no longer toggles fullscreen
  • UI labels: "Latency" → "Players", "Stats" → "Uptime", with contextual notes
  • Fix Signal.set() bug: stale #hasCapturedOldValue state when set() was called with no subscribers caused missed notifications on subsequent sets — this broke controls after collapsing and re-expanding a game
  • Fix stats desync: round seconds instead of truncating so counters tick over together
  • Simplify feedback timeout to use plain setTimeout

Test plan

  • Expand a game, press keys to control, collapse, re-expand, verify controls still work
  • Verify Enter only sends "start" button, Space/click toggle expand
  • Check stats panel shows Uptime/Players on the right, controls on the left
  • Verify uptime stats (emulation/video/audio) stay in sync

🤖 Generated with Claude Code

- Split expanded view into left (controls) and right (stats) panels
- Cap video at 4x native resolution (640x576)
- Enter key now only triggers Game Boy start button, not fullscreen toggle
- Rename "Latency" header to "Players" and "Stats" to "Uptime"
- Show latency note only when players are present
- Add note about emulation pausing when no viewers
- Round stats seconds instead of truncating to fix desync
- Simplify feedback timeout to use plain setTimeout
- Fix Signal.set() bug where stale #hasCapturedOldValue state caused
  missed notifications when set() was called with no subscribers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request contains updates across multiple components of the moq-boy project. The About section documentation text is simplified in multiple files. The UI layout is refactored to split a single controls container into separate left and right panels with updated keyboard handling (Space key only). Styling is adjusted to accommodate the new panel layout. The signals package version is bumped to 0.1.5 with a bug fix that clears old-value state in Signal.set() when there are no subscribers. The Rust backend changes how duration statistics are reported by rounding floating-point values instead of truncating them.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main changes: UI improvements (split panels, label updates, key binding changes) and a critical bug fix (Signal notification issue) that motivated the changeset.
Description check ✅ Passed The description provides a clear, well-structured summary directly related to the changeset, covering UI improvements, specific bug fixes, test plan, and technical details without being off-topic.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch moq-boy-fixes
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch moq-boy-fixes

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@js/moq-boy/src/index.ts`:
- Around line 349-350: The idle timeout stored in feedbackTimeout (declared at
top) may survive card disposal because clearTimeout is only invoked on
deactivation; update the disposal cleanup to always clear it by adding
clearTimeout(feedbackTimeout) inside the this.#signals.cleanup() handler (so
when GameCard.close()/disposal runs the timeout is cancelled); ensure the same
feedbackTimeout used in _sendCommand is referenced and cleared to prevent
lingering timers.

In `@js/moq-boy/src/styles.ts`:
- Around line 15-16: The expanded layout uses a horizontal flex row with both
side panels fixed at 240px and flex-shrink: 0 which causes overflow on narrow
phones; update the CSS in js/moq-boy/src/styles.ts to make the layout responsive
by either (A) adding a media query (e.g., max-width ~420px) that switches the
container with display: flex; flex-direction: column so the side panels stack
under the main card, or (B) allow the side panels to shrink by
removing/overriding flex-shrink: 0 and using min-width/max-width constraints so
they can go below 240px on small screens; ensure the same change is applied to
the other expanded layout block mentioned (lines ~75-81) and verify touch
controls in js/moq-boy/src/index.ts are reachable when the layout collapses.

In `@js/signals/src/index.ts`:
- Around line 80-85: The early-return branch that resets old-value state (when
this.#subscribers.size === 0 && this.#changed.size === 0) must also clear the
stale this.#forceNotify flag; update that branch (the block that sets
this.#hasCapturedOldValue = false and this.#oldValue = undefined) to explicitly
set this.#forceNotify = false before returning so the instance won't wrongly
force a future notification.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 11289979-0b1b-429f-a914-793a77b35058

📥 Commits

Reviewing files that changed from the base of the PR and between b6369d9 and 42375b6.

📒 Files selected for processing (7)
  • demo/boy/src/index.ts
  • js/moq-boy/src/element.ts
  • js/moq-boy/src/index.ts
  • js/moq-boy/src/styles.ts
  • js/signals/package.json
  • js/signals/src/index.ts
  • rs/moq-boy/src/main.rs

Comment thread js/moq-boy/src/index.ts Outdated
Comment on lines 349 to 350
let feedbackTimeout: ReturnType<typeof setTimeout> | undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "index.ts" -path "*/js/moq-boy/src/*" | head -5

Repository: moq-dev/moq

Length of output: 80


🏁 Script executed:

cd js/moq-boy && git ls-files src/ | head -20

Repository: moq-dev/moq

Length of output: 96


🏁 Script executed:

wc -l ./js/moq-boy/src/index.ts

Repository: moq-dev/moq

Length of output: 84


🏁 Script executed:

cat -n ./js/moq-boy/src/index.ts | sed -n '340,410p'

Repository: moq-dev/moq

Length of output: 2875


🏁 Script executed:

cat -n ./js/moq-boy/src/index.ts | sed -n '1,100p' | grep -E "(`#signals`|Signals)"

Repository: moq-dev/moq

Length of output: 468


🏁 Script executed:

cat -n ./js/moq-boy/src/index.ts | grep -n "this.#signals" | head -15

Repository: moq-dev/moq

Length of output: 952


🏁 Script executed:

cat -n ./js/moq-boy/src/index.ts | sed -n '550,593p'

Repository: moq-dev/moq

Length of output: 1650


🏁 Script executed:

cat -n ./js/moq-boy/src/index.ts | grep -n "close()" | head -20

Repository: moq-dev/moq

Length of output: 709


Clear the idle timeout when the card is disposed.

The feedbackTimeout set in _sendCommand (line 397) can persist after GameCard.close() if the card is disposed while the timeout is pending. Currently, clearTimeout is only called during the deactivation path (line 358), which doesn't guarantee cleanup on disposal. Add clearTimeout(feedbackTimeout) via this.#signals.cleanup() to ensure the timeout is always cleared when the card is disposed.

Suggested fix
 		const currentViewerId = new Moq.Signals.Signal<string | undefined>(undefined);
 		const feedbackActive = new Moq.Signals.Signal(false);
 		let feedbackTimeout: ReturnType<typeof setTimeout> | undefined;
+		this.#signals.cleanup(() => {
+			if (feedbackTimeout !== undefined) {
+				clearTimeout(feedbackTimeout);
+			}
+		});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let feedbackTimeout: ReturnType<typeof setTimeout> | undefined;
const currentViewerId = new Moq.Signals.Signal<string | undefined>(undefined);
const feedbackActive = new Moq.Signals.Signal(false);
let feedbackTimeout: ReturnType<typeof setTimeout> | undefined;
this.#signals.cleanup(() => {
if (feedbackTimeout !== undefined) {
clearTimeout(feedbackTimeout);
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@js/moq-boy/src/index.ts` around lines 349 - 350, The idle timeout stored in
feedbackTimeout (declared at top) may survive card disposal because clearTimeout
is only invoked on deactivation; update the disposal cleanup to always clear it
by adding clearTimeout(feedbackTimeout) inside the this.#signals.cleanup()
handler (so when GameCard.close()/disposal runs the timeout is cancelled);
ensure the same feedbackTimeout used in _sendCommand is referenced and cleared
to prevent lingering timers.

Comment thread js/moq-boy/src/styles.ts Outdated
Comment on lines 15 to 16
display: flex; flex-direction: row; justify-content: space-around;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the expanded layout responsive below phone widths.

Both side panels are fixed at 240px with flex-shrink: 0, so the expanded card overflows on common mobile widths before the video gets any space. Since the controls are touch-enabled in js/moq-boy/src/index.ts, this blocks the mobile/touch flow. Stack the panels under a breakpoint, or let them shrink below 240px.

Suggested fix
 	.card.expanded {
 		width: 100%; height: 100%;
 		border-radius: 0; border: none; aspect-ratio: auto;
 		display: flex; flex-direction: row; justify-content: space-around;
 	}
@@
 	.card.expanded .panel {
 		display: flex; flex-direction: column; align-items: center; justify-content: center;
 		width: 240px; background: `#111`; flex-shrink: 0;
 		padding: 1rem; gap: 1rem;
 	}
 	.card.expanded .panel-left { border-right: 1px solid `#333`; order: -1; }
 	.card.expanded .panel-right { border-left: 1px solid `#333`; }
+
+	`@media` (max-width: 900px) {
+		.card.expanded {
+			flex-direction: column;
+			justify-content: flex-start;
+			overflow-y: auto;
+		}
+
+		.card.expanded .panel {
+			width: 100%;
+			max-width: none;
+		}
+
+		.card.expanded .panel-left,
+		.card.expanded .panel-right {
+			order: 0;
+			border-left: none;
+			border-right: none;
+		}
+	}

Also applies to: 75-81

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@js/moq-boy/src/styles.ts` around lines 15 - 16, The expanded layout uses a
horizontal flex row with both side panels fixed at 240px and flex-shrink: 0
which causes overflow on narrow phones; update the CSS in
js/moq-boy/src/styles.ts to make the layout responsive by either (A) adding a
media query (e.g., max-width ~420px) that switches the container with display:
flex; flex-direction: column so the side panels stack under the main card, or
(B) allow the side panels to shrink by removing/overriding flex-shrink: 0 and
using min-width/max-width constraints so they can go below 240px on small
screens; ensure the same change is applied to the other expanded layout block
mentioned (lines ~75-81) and verify touch controls in js/moq-boy/src/index.ts
are reachable when the layout collapses.

Comment thread js/signals/src/index.ts Outdated
Comment on lines +80 to +85
// Reset old value state since no flush will occur to clear it.
if (this.#subscribers.size === 0 && this.#changed.size === 0) {
this.#hasCapturedOldValue = false;
this.#oldValue = undefined;
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clear stale #forceNotify on the no-listener early return.

Line 77 may set #forceNotify = true, but Lines 81-85 return without clearing it. That stale flag can force a later notification even when there is no net value change.

Proposed fix
 		if (this.#subscribers.size === 0 && this.#changed.size === 0) {
 			this.#hasCapturedOldValue = false;
 			this.#oldValue = undefined;
+			this.#forceNotify = false;
 			return;
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Reset old value state since no flush will occur to clear it.
if (this.#subscribers.size === 0 && this.#changed.size === 0) {
this.#hasCapturedOldValue = false;
this.#oldValue = undefined;
return;
}
// Reset old value state since no flush will occur to clear it.
if (this.#subscribers.size === 0 && this.#changed.size === 0) {
this.#hasCapturedOldValue = false;
this.#oldValue = undefined;
this.#forceNotify = false;
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@js/signals/src/index.ts` around lines 80 - 85, The early-return branch that
resets old-value state (when this.#subscribers.size === 0 && this.#changed.size
=== 0) must also clear the stale this.#forceNotify flag; update that branch (the
block that sets this.#hasCapturedOldValue = false and this.#oldValue =
undefined) to explicitly set this.#forceNotify = false before returning so the
instance won't wrongly force a future notification.

- Rename GameCard -> Game as a non-UI backend class with member signals
- Add SolidJS UI layer (components, context, hooks) matching @moq/watch patterns
- Replace styles.ts with proper CSS files supporting light/dark mode
- Add zod/mini validation for status JSON
- Add configurable game/viewer path prefixes for production vs localhost
- Pass signals directly to Watch APIs (enabled, muted) instead of effect wrappers
- Fix responsive layout for narrow screens (stack panels below video)
- Fix stale forceNotify flag bug in @moq/signals
- Update demo app to use <moq-boy-ui><moq-boy> web component
- Update docs with new prefix architecture

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@kixelated kixelated changed the title moq-boy: UI improvements and fix signal notification bug moq-boy: Review+revamp JS player Apr 7, 2026
@kixelated kixelated merged commit f76acbb into main Apr 7, 2026
2 checks passed
@kixelated kixelated deleted the moq-boy-fixes branch April 7, 2026 01:19
@moq-bot moq-bot Bot mentioned this pull request Apr 4, 2026
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