Skip to content

refactor(app): give formatTime a home — a leaf format.js, one fewer host hook (R3a)#895

Merged
byrongamatos merged 1 commit into
mainfrom
feat/r3-carve-format
Jul 11, 2026
Merged

refactor(app): give formatTime a home — a leaf format.js, one fewer host hook (R3a)#895
byrongamatos merged 1 commit into
mainfrom
feat/r3-carve-format

Conversation

@byrongamatos

@byrongamatos byrongamatos commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Stacked on #894. static/js/format.js — 17 lines, one function. Retires the formatTime hook: 12 → 11.

Why a module for one function

formatTime was a host hookloops.js and section-practice.js both reached back through the seam for it.

It is also, by pure accident of who calls it, inside the dependency closure of the library carve that comes next. Leaving it there would have made loops.js and section-practice.js import the library in order to format a timestamp — nonsense, and a cycle waiting to happen.

Same rule as the transport carve (#894): a hook is a cycle you agreed to live with; an import is a dependency you actually have. formatTime has a real owner. It just isn't app.js, and it certainly isn't the library. Give it a home and both consumers import it directly.

A leaf on purpose. Anything else that turns out to be a shared pure formatter belongs here too — nothing does yet. I checked: formatBadge, formatBadgeInline, _safeImageUrl and _fetchJsonOrThrow all look general, but none has a caller outside the library, so none of them moved. YAGNI applies to modules too.

node 1040/1040 · host contract 2/2 · ESLint 0 · Codex 0.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved time display consistency across loop controls, saved loops, and section-practice tooltips.
    • Times now reliably appear in a clear M:SS format, including seconds with leading zeroes.
    • Updated loop labels and fallback text to use the same formatting throughout the app.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 267a0b09-e4c3-4dc0-9528-a6978843f332

📥 Commits

Reviewing files that changed from the base of the PR and between c5f85e6 and e1d9463.

📒 Files selected for processing (4)
  • static/app.js
  • static/js/format.js
  • static/js/loops.js
  • static/js/section-practice.js

📝 Walkthrough

Walkthrough

The PR extracts formatTime into static/js/format.js, imports it in app.js, loops.js, and section-practice.js, and removes it from the configureHost host bindings.

Changes

Time formatter extraction

Layer / File(s) Summary
Shared time formatter
static/js/format.js, static/app.js
Adds the exported formatTime implementation and removes the duplicate inline helper from app.js.
Direct formatter consumers
static/app.js, static/js/loops.js, static/js/section-practice.js
Loop labels, saved-loop options, and section tooltips use the imported formatter; host wiring no longer provides formatTime.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • got-feedBack/feedBack#887: Changes the host seam and validates hook wiring, overlapping with this PR’s removal of formatTime from host bindings.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% 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 clearly describes the main refactor: moving formatTime into a new leaf module and reducing one host hook.
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.
✨ 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 feat/r3-carve-format

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

…ost hook (R3a)

static/js/format.js (17). One function. Retires the formatTime hook: 12 -> 11.

WHY A MODULE FOR ONE FUNCTION. formatTime was a host hook — loops.js and
section-practice.js both reached back through the seam for it. It is ALSO, by pure
accident of who calls it, inside the dependency closure of the library carve that comes
next. Leaving it there would have made loops.js and section-practice.js import the
LIBRARY in order to format a timestamp — nonsense, and a cycle waiting to happen.

Same rule as the transport carve: a hook is a cycle you agreed to live with; an import is
a dependency you actually have. formatTime has a real owner. It just isn't app.js, and it
certainly isn't the library. Give it a home and both consumers import it directly.

A leaf on purpose. Anything else that turns out to be a shared pure formatter belongs
here too; nothing does yet (I checked — formatBadge, _safeImageUrl and _fetchJsonOrThrow
have no callers outside the library), so nothing else is here.

node 1040/1040, host contract 2/2, ESLint 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos
byrongamatos force-pushed the feat/r3-carve-format branch from c5f85e6 to e1d9463 Compare July 11, 2026 21:26
@byrongamatos
byrongamatos merged commit 09f7e45 into main Jul 11, 2026
4 checks passed
@byrongamatos
byrongamatos deleted the feat/r3-carve-format branch July 11, 2026 21:27

@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: 1

🧹 Nitpick comments (2)
static/js/juce-audio.js (1)

201-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale window.jucePlayer truthiness guards no longer match what they gate.

Two spots still branch on window.jucePlayer even though jucePlayer is now a directly-imported, always-defined singleton object from transport.js:

  • Line 215: if (window.jucePlayer) { jucePlayer._dur = dur; ... } — writes to the imported jucePlayer, but the guard checks a different reference (window.jucePlayer).
  • Line 273: const pos = (window.jucePlayer ? jucePlayer.currentTime : 0) || 0; — same pattern; if the guard is ever false, pos silently falls back to 0 even though jucePlayer.currentTime is perfectly readable.

Under standard ES module evaluation (no top-level await in this graph), window.jucePlayer = jucePlayer in app.js runs synchronously before any of these deferred callbacks can fire, so this isn't currently reachable as a live bug — but it's a leftover from the old host-hook pattern (where host.jucePlayer() could genuinely be undefined) that no longer applies now that the binding is a direct import. Worth cleaning up as part of this exact migration rather than leaving a guard that checks the wrong thing.

♻️ Proposed cleanup
-            if (window.jucePlayer) {
-                jucePlayer._dur = dur;
-                jucePlayer._pos = pos;
-                jucePlayer._pollAt = performance.now();
-            }
+            jucePlayer._dur = dur;
+            jucePlayer._pos = pos;
+            jucePlayer._pollAt = performance.now();
-        const pos = (window.jucePlayer ? jucePlayer.currentTime : 0) || 0;
+        const pos = jucePlayer.currentTime || 0;

Also applies to: 270-273

🤖 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 `@static/js/juce-audio.js` around lines 201 - 219, Remove the stale
window.jucePlayer truthiness guards in the deferred playback logic. In the block
updating _dur, _pos, and _pollAt, access the imported jucePlayer directly, and
in the position calculation use jucePlayer.currentTime directly while preserving
the existing || 0 fallback.
tests/js/song_event_payload.test.js (1)

132-149: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

allFrontendSources() is duplicated verbatim in tests/js/song_seek.test.js.

Same function, same comment block, copy-pasted across two test files. A directory-layout change (e.g. moving static/js or adding a nested subfolder) now has to be fixed in both places, and it's easy for the copies to silently drift.

Consider extracting this into a small shared test helper (e.g. tests/js/helpers/frontend-sources.js) that both files import.

🤖 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 `@tests/js/song_event_payload.test.js` around lines 132 - 149, Extract the
duplicated allFrontendSources helper and its source-discovery logic into a
shared tests/js helper module, then import and use that shared function in both
song_event_payload.test.js and song_seek.test.js. Preserve the existing behavior
of reading static/app.js and JavaScript files under static/js.
🤖 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.

Inline comments:
In `@static/js/transport.js`:
- Around line 305-329: The JUCE branch of togglePlay must capture _audioSeekGen
before each asynchronous jucePlayer.pause() or jucePlayer.play() operation, then
return without updating state or emitting song:* events if the generation
changed while awaiting. Preserve the existing pause/play behavior when the
captured generation still matches.

---

Nitpick comments:
In `@static/js/juce-audio.js`:
- Around line 201-219: Remove the stale window.jucePlayer truthiness guards in
the deferred playback logic. In the block updating _dur, _pos, and _pollAt,
access the imported jucePlayer directly, and in the position calculation use
jucePlayer.currentTime directly while preserving the existing || 0 fallback.

In `@tests/js/song_event_payload.test.js`:
- Around line 132-149: Extract the duplicated allFrontendSources helper and its
source-discovery logic into a shared tests/js helper module, then import and use
that shared function in both song_event_payload.test.js and song_seek.test.js.
Preserve the existing behavior of reading static/app.js and JavaScript files
under static/js.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f61774e8-da58-4100-afcd-87a0e39d22da

📥 Commits

Reviewing files that changed from the base of the PR and between 8d0e270 and c5f85e6.

📒 Files selected for processing (11)
  • static/app.js
  • static/js/count-in.js
  • static/js/format.js
  • static/js/juce-audio.js
  • static/js/loops.js
  • static/js/section-practice.js
  • static/js/transport.js
  • tests/js/play_button_reroute_guard.test.js
  • tests/js/playback_app_adapter.test.js
  • tests/js/song_event_payload.test.js
  • tests/js/song_seek.test.js

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (2)
static/js/juce-audio.js (1)

201-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale window.jucePlayer truthiness guards no longer match what they gate.

Two spots still branch on window.jucePlayer even though jucePlayer is now a directly-imported, always-defined singleton object from transport.js:

  • Line 215: if (window.jucePlayer) { jucePlayer._dur = dur; ... } — writes to the imported jucePlayer, but the guard checks a different reference (window.jucePlayer).
  • Line 273: const pos = (window.jucePlayer ? jucePlayer.currentTime : 0) || 0; — same pattern; if the guard is ever false, pos silently falls back to 0 even though jucePlayer.currentTime is perfectly readable.

Under standard ES module evaluation (no top-level await in this graph), window.jucePlayer = jucePlayer in app.js runs synchronously before any of these deferred callbacks can fire, so this isn't currently reachable as a live bug — but it's a leftover from the old host-hook pattern (where host.jucePlayer() could genuinely be undefined) that no longer applies now that the binding is a direct import. Worth cleaning up as part of this exact migration rather than leaving a guard that checks the wrong thing.

♻️ Proposed cleanup
-            if (window.jucePlayer) {
-                jucePlayer._dur = dur;
-                jucePlayer._pos = pos;
-                jucePlayer._pollAt = performance.now();
-            }
+            jucePlayer._dur = dur;
+            jucePlayer._pos = pos;
+            jucePlayer._pollAt = performance.now();
-        const pos = (window.jucePlayer ? jucePlayer.currentTime : 0) || 0;
+        const pos = jucePlayer.currentTime || 0;

Also applies to: 270-273

🤖 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 `@static/js/juce-audio.js` around lines 201 - 219, Remove the stale
window.jucePlayer truthiness guards in the deferred playback logic. In the block
updating _dur, _pos, and _pollAt, access the imported jucePlayer directly, and
in the position calculation use jucePlayer.currentTime directly while preserving
the existing || 0 fallback.
tests/js/song_event_payload.test.js (1)

132-149: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

allFrontendSources() is duplicated verbatim in tests/js/song_seek.test.js.

Same function, same comment block, copy-pasted across two test files. A directory-layout change (e.g. moving static/js or adding a nested subfolder) now has to be fixed in both places, and it's easy for the copies to silently drift.

Consider extracting this into a small shared test helper (e.g. tests/js/helpers/frontend-sources.js) that both files import.

🤖 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 `@tests/js/song_event_payload.test.js` around lines 132 - 149, Extract the
duplicated allFrontendSources helper and its source-discovery logic into a
shared tests/js helper module, then import and use that shared function in both
song_event_payload.test.js and song_seek.test.js. Preserve the existing behavior
of reading static/app.js and JavaScript files under static/js.
🤖 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.

Inline comments:
In `@static/js/transport.js`:
- Around line 305-329: The JUCE branch of togglePlay must capture _audioSeekGen
before each asynchronous jucePlayer.pause() or jucePlayer.play() operation, then
return without updating state or emitting song:* events if the generation
changed while awaiting. Preserve the existing pause/play behavior when the
captured generation still matches.

---

Nitpick comments:
In `@static/js/juce-audio.js`:
- Around line 201-219: Remove the stale window.jucePlayer truthiness guards in
the deferred playback logic. In the block updating _dur, _pos, and _pollAt,
access the imported jucePlayer directly, and in the position calculation use
jucePlayer.currentTime directly while preserving the existing || 0 fallback.

In `@tests/js/song_event_payload.test.js`:
- Around line 132-149: Extract the duplicated allFrontendSources helper and its
source-discovery logic into a shared tests/js helper module, then import and use
that shared function in both song_event_payload.test.js and song_seek.test.js.
Preserve the existing behavior of reading static/app.js and JavaScript files
under static/js.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f61774e8-da58-4100-afcd-87a0e39d22da

📥 Commits

Reviewing files that changed from the base of the PR and between 8d0e270 and c5f85e6.

📒 Files selected for processing (11)
  • static/app.js
  • static/js/count-in.js
  • static/js/format.js
  • static/js/juce-audio.js
  • static/js/loops.js
  • static/js/section-practice.js
  • static/js/transport.js
  • tests/js/play_button_reroute_guard.test.js
  • tests/js/playback_app_adapter.test.js
  • tests/js/song_event_payload.test.js
  • tests/js/song_seek.test.js
🛑 Comments failed to post (1)
static/js/transport.js (1)

305-329: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file and surrounding symbols first
ast-grep outline static/js/transport.js --view expanded || true

printf '\n--- search for _audioSeekGen / _resetAudioSeekState / jucePlayer ---\n'
rg -n "_audioSeekGen|_resetAudioSeekState|jucePlayer|togglePlay|sessionGen|attempt" static/js/transport.js

printf '\n--- relevant slices ---\n'
sed -n '1,220p' static/js/transport.js
printf '\n--- middle slice ---\n'
sed -n '220,420p' static/js/transport.js

Repository: got-feedBack/feedBack

Length of output: 20365


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect any tests or callers that might rely on JUCE togglePlay ordering/state
rg -n "song:pause|song:play|song:resume|_audioSeekGen|_resetAudioSeekState|togglePlay\\(" static test tests src . || true

Repository: got-feedBack/feedBack

Length of output: 34969


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether JUCE mode state changes can happen during awaits from other paths
rg -n "window\\._juceMode|S\\.isPlaying|setPlayButtonState\\(|feedBack\\.isPlaying|emit\\('song:(pause|play|resume)'" static/js/transport.js static/js || true

Repository: got-feedBack/feedBack

Length of output: 8881


Mirror the HTML5 session guard in the JUCE path. Capture _audioSeekGen before the await and return early if it changed, so a song switch/teardown can’t let an old JUCE pause/play resolution overwrite the current session’s S.isPlaying or song:* events.

🧰 Tools
🪛 ast-grep (0.44.1)

[error] 314-314: React's useState should not be directly called
Context: setPlayButtonState(false)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 321-321: React's useState should not be directly called
Context: setPlayButtonState(true)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)

🤖 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 `@static/js/transport.js` around lines 305 - 329, The JUCE branch of togglePlay
must capture _audioSeekGen before each asynchronous jucePlayer.pause() or
jucePlayer.play() operation, then return without updating state or emitting
song:* events if the generation changed while awaiting. Preserve the existing
pause/play behavior when the captured generation still matches.

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