Skip to content

fix: align voice memo waveform highlight with playback position#5917

Open
janicduplessis wants to merge 6 commits into
developfrom
@janic/voice-memo-waveform-alignment
Open

fix: align voice memo waveform highlight with playback position#5917
janicduplessis wants to merge 6 commits into
developfrom
@janic/voice-memo-waveform-alignment

Conversation

@janicduplessis

@janicduplessis janicduplessis commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

The voice memo waveform highlight didn't line up with the actual playback position — it sat a few candles left of the true position and never filled the waveform at the end of a memo. Noticed while working on #5916 (waveform seek), but independent of it: the misalignment is visible during plain playback.

Changes

  • The playhead position is now converted to a candle index once, against the candles actually drawn, and it can reach the last candle. Before, the position was rounded in two places at different scales, which put the highlight off by up to half a candle, and an off-by-one comparison meant the last candle could never light up.
  • Filler candles now light up once the playhead passes them. When a memo has fewer waveform values than fit in the row, the waveform pads it with filler candles to fill the width — and the color logic checked "is this filler?" before "has the playhead passed it?", so filler bars stayed grey forever (e.g. 51 drawn vs 50 values left one dead bar at the end). The playhead check now wins, and the highlight is spread over all drawn candles (filler included) so the whole strip fills at the end of playback. Candles past the playhead keep the filler/unplayed distinction for themes that color them differently.

How did I test?

  • iOS simulator (iPhone 17 Pro): watched the highlight edge frame-by-frame through a full playback — it tracks the elapsed time and fills the waveform at the end (before, it stopped ~5 candles short).
  • Checked the bar colors with the playhead at the end — every drawn bar, including the filler bar, takes the active color.
  • Web (Chrome against a dev ship): checked the SVG candle fills in the DOM during a full playback — the highlight covers the drawn strip (42/84 candles when parked at 50%) and reaches 84/84, filler included, at the end.
  • Before/after videos of a full playback below.

Risks and impact

  • Safe to rollback without consulting PR author? Yes
  • Affects important code area:
    • Onboarding
    • State / providers
    • Message sync
    • Channel display
    • Notifications
    • Other: voice memo playback

Rollback plan

Revert these commits.

Screenshots / videos

Before — highlight lags and never reaches the end:

5917-before.mp4

After — highlight tracks playback and fills the waveform:

5917-after.mp4

Web — candle state sampled live from the DOM during playback, rendered for visibility (the long parked stretch is the memo buffering):

web-validation.mp4

@janicduplessis janicduplessis force-pushed the @janic/voice-memo-waveform-alignment branch from 505b28d to 00133ff Compare June 10, 2026 03:37
@janicduplessis janicduplessis force-pushed the @janic/voice-memo-waveform-alignment branch from 00133ff to 04f43ba Compare June 10, 2026 03:42
@janicduplessis janicduplessis changed the base branch from @janic/voice-memo-waveform-seek to develop June 10, 2026 03:43
@janicduplessis janicduplessis marked this pull request as ready for review June 10, 2026 05:15

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d9c9c2f5c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 111 to +115
height={Math.max(5, layout.height * (heightRatio ?? 0))}
r={40}
color={
heightRatio == null
? candleInactiveColor
: index < scaledCandlePlaybackPosition
? candleActiveColor
index < scaledCandlePlaybackPosition
? candleActiveColor

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mirror the waveform playback fix on web

When this component renders on web, React Native resolves Waveform.web.tsx instead of this file; that implementation still leaves null padding candles inactive and only rescales when values.length > maxVisibleCandleCount. As a result, web voice memos with padded previews (for example 50 samples in a 51-candle container, or the 15-bar dummy waveform) still do not fill the trailing candles at the end of playback, even though the native path now does. Apply the same scaling/color ordering there if this playback alignment fix is intended to cover voice memos across platforms.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — applied the same changes to Waveform.web.tsx in 4f651b1: the playhead rescales over the drawn candle count whenever it differs from the value count (padded case included, not just simplified), and the playhead check now wins over the filler check in the SVG fill. Mirrors the native logic that was validated by pixel-classifying the rendered candles at the end of playback.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4f651b176f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
return clamp(
Math.floor((progress.currentTime / progress.duration) * candleCount),
Math.round((progress.currentTime / progress.duration) * candleCount),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve fractional playback before scaling padded waveforms

For voice memos whose preview has fewer samples than the rendered candle count, this rounds the progress against the short candleCount before Waveform rescales it across the padded candles. That makes candlePlaybackPosition become candleCount as soon as playback reaches (candleCount - 0.5) / candleCount, after which the Waveform scaling lights every padded candle even though audio is still playing; for the 15-bar dummy waveform, a 60s memo appears fully highlighted from about 58s onward. Passing a fractional position through to Waveform or rounding only after the final drawn count is known would keep the highlight aligned until actual completion.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right — the value-domain rounding before the Waveform's rescale double-quantized the position, so short previews (like the 15-bar dummy) hit "full" up to half a value-candle early. Fixed in fb4ac8e: candlePlaybackPosition is now passed through as a fractional position and the only quantization left is the index < scaled comparison against the candles actually drawn, on both the native and web waveforms.

Copilot AI 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.

Pull request overview

This PR fixes misalignment between the voice memo waveform’s highlighted “played” region and the actual playback position by keeping the playhead as a fraction until the waveform scales it against the candles actually rendered (including padded filler candles), allowing the highlight to reach the final candle.

Changes:

  • Pass a fractional playhead position through VoiceMemoBlock (no early floor/candleCount - 1 clamp), enabling the last candle to highlight.
  • Rescale playhead position in Waveform / Waveform.web when the rendered candle count differs from the input value count (including padded filler), and allow filler candles to highlight after the playhead passes them.
  • Adjust candle fill precedence so the playhead-highlight check wins over “filler candle” coloring.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
packages/app/ui/components/PostContent/BlockRenderer.tsx Computes playhead position as a fraction and clamps to candleCount so the final candle can highlight.
packages/app/ui/components/AudioRecorder/Waveform.tsx Rescales playhead over rendered candle count and allows filler candles to highlight past the playhead.
packages/app/ui/components/AudioRecorder/Waveform.web.tsx Mirrors native waveform logic changes for web (SVG) rendering.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 74 to 78
const scaledCandlePlaybackPosition = useMemo(() => {
if (
maxVisibleCandleCount == null ||
values.length <= maxVisibleCandleCount
values.length === maxVisibleCandleCount
) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added the guard in ea1e2f5values.length === 0 short-circuits to the raw position in both Waveform.tsx and Waveform.web.tsx (the caller's position is already 0 for an empty preview, so no NaN reaches the candle comparison).

Comment on lines 73 to 77
const scaledCandlePlaybackPosition = useMemo(() => {
if (
maxVisibleCandleCount == null ||
values.length <= maxVisibleCandleCount
values.length === maxVisibleCandleCount
) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same guard added to the web variant in ea1e2f5.

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.

2 participants