Skip to content

TT-7643 fix: name a Phrase BT take for its step language so it cannot collide - #561

Merged
gtryus merged 4 commits into
developfrom
TT-7643_pbt-cross-language-audio
Sep 3, 2026
Merged

TT-7643 fix: name a Phrase BT take for its step language so it cannot collide#561
gtryus merged 4 commits into
developfrom
TT-7643_pbt-cross-language-audio

Conversation

@nabalone

@nabalone nabalone commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Problem

TT-7643 — with a Phrase Back Translation step per language (Sena, Hebrew, English), audio recorded in one language plays in another language's PBT step.

On the desktop app a take's audio is resolved by file name, not by id. useFetchMediaUrl calls dataPath(mediafile.attributes.audioUrl, PathType.MEDIA), which maps to <offlineData>/media/<basename(audioUrl)> and returns that local file if it exists — the mediafile id is never consulted. store/upload/actions.tsx also stages every online upload into that same folder, so the cache is populated as you record.

The upload name is passageDefaultFilename(...) + config.buildFilenamePostfix(unitIndex, sourceVersion). It carried the passage, the segment index and the source version — but not the step language. A team configures one PBT step per language and every one of them records the same segment of the same vernacular, so Hebrew segment 1 uploaded under exactly the name Sena segment 1 had already cached. First file cached wins, and that is Sena.

The naming is visible in a real cache:

~/transcriber/media/GEN001_001-010backtranslation1_v1_lofflin.ogg
                    GEN001_001-010backtranslation2_v1s1_lofflin.ogg
                    GEN001_001-010backtranslation3_v1s2_lofflin.ogg

One file per segment, no language anywhere in the name.

What this supersedes

The first two commits on this branch were aimed at component reuse, on the reading that the step instance carried its take across a step change. That is not what the ticket reports: the ticket video is the desktop layout, and PassageDetailGrids has had <Paper key={currentstep}> all along, so the step was already remounting. Those commits stay because they are correct on their own terms — the mobile route lacked that parity, and the segment hook's reset had to key on the language bucket as well as the audio (Devin's finding) — but they are hardening, not the fix. The row-level language filter from #490 is likewise sound: a CT test on the real step shows a Hebrew step never fetches a Sena take.

Changes

  • guidedPhraseRecord/types.tsbuildFilenamePostfix takes the step bcp47 and appends it: backtranslation1_v1_seh vs backtranslation1_v1_he. Steps with no configured language (Careful Speech) keep the names they always had, so existing files are untouched.
  • PassageDetailGuidedPhraseRecord.tsx — passes stepLanguageBcp47 into the postfix builder.
  • cypress/support/pbtHarness.tsx — the fake server stamped every uploaded take English|en instead of the language that was posted, which hid whether the step language reached the upload at all.

Retained from the earlier commits (hardening, not the fix):

  • routes/PassageDetail.tsxkey={currentstep} on the guided-record steps in MobileStep, matching desktop and the TT-7503 keyed transcription step.
  • PassageDetailGuidedPhraseRecord.tsx — per-step reset gated on `${mediafileId}|${currentstep}`; claimRanRef released with it.
  • carefulSpeech/useGuidedPhraseSegments.tsresetForScope keys on the BT:<bcp47> bucket as well as the mediafile, so a step change is not rejected as a no-op.
  • carefulSpeech/claimLegacyPhraseBt.ts — the legacy language claim is skipped once another BT:<bcp47> bucket has regions, instead of stamping every untagged take with whichever step opened first.

Test plan

Failing-before / passing-after, all against unfixed source:

  • guidedPhraseRecord/types.test.tsbuildFilenamePostfix separates the languages of the same segment.
  • PassageDetailGuidedPhraseRecord.stepScope.test.tsx — "names a take so it cannot collide with another language of the same segment": drives the real component through a Sena step then a Hebrew step and reads the filename it hands MediaRecord. Pre-fix both steps produce GEN001_014-019backtranslation1_v1_plan.
  • PassageDetailPhraseBackTranslate.cy.tsx — "uploads a take under a name no other language can take": records a real take on a Hebrew step and asserts the posted original-file carries the language.

Also on this branch: the step-scope reset tests, useGuidedPhraseSegments.test.tsx (real hook, bucket change on the same audio), the "PBT language scoping" CT pair, and 3 new claimLegacyPhraseBt cases.

  • npx jest (full renderer suite) → 1389 pass, 3 skipped; 1 pre-existing unrelated failure (EditReferenceDropdown.test.tsx, MUI Drawer module resolution) present on develop too.
  • tsc --noEmit clean; Prettier clean on the touched files.
  • Manual: on a team with a PBT step per language, record every segment in one language, then record them in another language's step, and confirm each step plays back its own take. Takes recorded before this change keep their colliding names — re-record to verify.

Known, not fixed here

Re-recording the same segment in the same language reuses the name too, so the earlier take's cached audio can play for the new one. Same defect class, outside this ticket — flagging rather than folding in, since a per-take unique token changes every filename users see in exports.

🤖 Generated with Claude Code

nabalone and others added 3 commits August 31, 2026 18:58
… step

A team can configure one Phrase Back Translation step per language, and
every one of them renders the same PassageDetailGuidedPhraseRecord
against the same vernacular audio. The mobile route rendered that
component unkeyed, so moving between two PBT steps reused the instance -
and the component resets its per-step state only when the vernacular
mediafile changes, which it does not here. The recording pass, the
segment it was parked on, the baseline boundaries, the optimistic greens
and the take mounted in its recorder all carried into the next
language's step, which then played the previous language's audio.

The route now keys the guided-record steps on currentstep, the way the
desktop grid already does with <Paper key={currentstep}> and the way the
adjacent transcription steps were keyed for TT-7503. The component also
scopes its own reset to the step as well as the mediafile, so a reused
instance still opens the next step from scratch.

The legacy claim had a second cross-language path: it stamped every
untagged take of its artifact type with the opening step's language.
That premise only holds for data recorded before takes carried a
language, in a project working in one language. Once the passage has
Phrase BT boundaries for another language an untagged take could belong
to either step, so the claim is now skipped there rather than handing
the take to whichever step happened to open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rs in the PBT harness

parseMediaLanguageBcp47 and phraseBtBoundaryRegionName already own the
`Name|bcp47` parse and the `BT:<bcp47>` bucket name; the harness had
grown its own copies of both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t changes

useGuidedPhraseSegments guarded its own reset on the mediafile id, so a
step change on the same vernacular was rejected as a no-op: bootstrapped
stayed true and ensureSegments short-circuited on the phrase string
already loaded. The next language's step opened on the previous
language's boundaries even though the parent had asked for a reset.

The bucket is part of that scope, not just the audio - each Phrase BT
step reads its own BT:<bcp47> regions off the same vernacular - so the
guard now keys on both, and the function is named resetForScope to say
so. Covered by a test against the real hook rather than the mocked one
(Devin review).

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

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 cross-language state/audio bleed when teams configure multiple Phrase Back Translation (PBT) guided-record steps (one per language) against the same vernacular audio. It ensures guided-record UI state and mounted takes are scoped to the current step/language, particularly on mobile where the route previously reused an unkeyed component instance.

Changes:

  • Key mobile guided-record steps by currentstep so navigating between adjacent per-language steps remounts the step UI instance.
  • Scope guided-record reset logic to ${mediafileId}|${currentstep} (not just mediafileId) and reset legacy-claim state per step.
  • Prevent claimLegacyPhraseBt from stamping untagged takes with a language once multi-language PBT boundaries exist, and add unit + Cypress coverage to lock in behavior.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/renderer/src/routes/PassageDetail.tsx Adds key={currentstep} to guided-record mobile steps to force remount between adjacent per-language steps.
src/renderer/src/components/PassageDetail/PassageDetailGuidedPhraseRecord.tsx Scopes reset behavior to step+mediafile and releases per-step legacy-claim gating when step changes.
src/renderer/src/components/PassageDetail/carefulSpeech/useGuidedPhraseSegments.ts Renames/reset logic to resetForScope and keys the boundary reset on (mediafileId, namedRegion) (language bucket).
src/renderer/src/components/PassageDetail/carefulSpeech/claimLegacyPhraseBt.ts Skips language-claiming untagged takes when other-language boundary buckets are present with regions.
src/renderer/src/components/PassageDetail/PassageDetailGuidedPhraseRecord.stepScope.test.tsx New test verifying a reused guided-record instance resets cleanly when currentstep changes.
src/renderer/src/components/PassageDetail/carefulSpeech/useGuidedPhraseSegments.test.tsx New test verifying boundary reset re-reads when only the bucket/language changes on the same audio.
src/renderer/src/components/PassageDetail/carefulSpeech/claimLegacyPhraseBt.test.ts Adds cases covering multi-language boundary presence and empty/own-bucket scenarios.
src/renderer/src/components/PassageDetail/PassageDetailCarefulSpeech.test.tsx Updates mocks to the new resetForScope API.
src/renderer/src/components/PassageDetail/PassageDetailPhraseBackTranslate.cy.tsx Adds CT coverage that only this step’s language take is fetched/played.
src/renderer/cypress/support/pbtHarness.tsx Extends the PBT harness to seed per-language takes/step language and record fileurl fetch IDs for assertions.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

… collide

The real cause of PBT audio from one language playing in another: on the
desktop app a take's audio is resolved by FILE NAME, not by id.
useFetchMediaUrl calls dataPath(mediafile.audioUrl, PathType.MEDIA), which
maps to <offlineData>/media/<basename> and hands back whatever file is
already sitting there.

The upload name is passageDefaultFilename + buildFilenamePostfix, which
carried the passage, the segment index and the source version but not the
step language. A team can configure one Phrase BT step per language, and
every one of them records the same segment of the same vernacular - so
Hebrew segment 1 uploaded under exactly the name Sena segment 1 had already
cached, and the first file cached is what played for both.

buildFilenamePostfix now takes the step bcp47 and appends it. Steps with no
configured language (Careful Speech) keep the names they always had, so
existing files are untouched.

Also fixes a harness defect that hid this: the fake server stamped every
uploaded take English|en rather than the language that was posted.

Note the earlier commits on this branch do not fix the reported defect - the
desktop route already remounted the step on currentstep. They stay because
they are correct on their own terms: the mobile route lacked that parity, and
the segment hook's reset had to key on the language bucket as well as the
audio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nabalone nabalone changed the title TT-7643 fix: keep a Phrase BT step's audio out of the next language's step TT-7643 fix: name a Phrase BT take for its step language so it cannot collide Sep 1, 2026
@nabalone
nabalone marked this pull request as ready for review September 1, 2026 20:59
@gtryus
gtryus merged commit 75cd184 into develop Sep 3, 2026
2 checks passed
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.

3 participants