Skip to content

fix(ux): customer-facing copy audit — hide implementation boundaries, add next-action guidance - #1034

Open
seonghobae wants to merge 31 commits into
developfrom
fix/customer-copy-next-action
Open

fix(ux): customer-facing copy audit — hide implementation boundaries, add next-action guidance#1034
seonghobae wants to merge 31 commits into
developfrom
fix/customer-copy-next-action

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Pull Request

Summary

This PR audits mounted customer-facing copy against the BandScope brand rule: hide implementation boundaries, preserve safe diagnosis, and always give the player a useful next action.

Current exact head: 537d2674a400c3d854102ad01606565c0cf022d7.

Buyer-visible changes

  • Removes implementation-oriented copy from mounted confidence, score, analysis, stem, groove, and workspace surfaces.
  • Routes previously hardcoded English score/analysis fallback messages through the existing en/ko dictionaries.
  • Keeps known-safe local-audio failure causes distinct after localization:
    • unreadable selected file → reselect the file;
    • project workspace preparation → check the save location and retry;
    • cache/temp workspace preparation → retry;
    • unknown bridge detail → fail closed to fixed format guidance without surfacing raw implementation text.
  • Gives browser not_found analysis jobs dedicated retry guidance instead of the misleading “analysis stopped partway through” message.
  • Uses count-neutral English song-timeline copy so one-section songs do not render 1 sections.
  • Localizes browser fallback progress labels, YouTube URL guidance, project-only guidance, score storage guidance, and other mounted rehearsal copy.

Regression coverage added in this branch

  • Workspace.timeline-summary.test.tsx: one-section English timeline grammar.
  • App.localized-source-error.test.tsx: Korean local-audio browser fallback stays Korean.
  • analysis.localized-errors.test.ts: Korean YouTube/project fallbacks, distinct allowlisted local-audio failure guidance, unknown-error redaction, and browser not_found semantics.
  • scoreStorage.localized-errors.test.ts: Korean desktop-only and malformed-response score errors.

These were added regression-first. Hosted RED evidence is not claimed because the successor fixes were pushed before the queued GitHub runners completed those intermediate heads.

Security / trust boundary

  • No new network, filesystem, dialog, URL-admission, IPC command, dependency, or credential path is introduced.
  • The native local-audio allowlist strings remain byte-for-byte compatible; the UI now maps those known-safe values to localized next-action keys.
  • Unknown local bridge errors remain fail-closed and are not surfaced verbatim.
  • YouTube URL admission remains unchanged; only buyer-visible validation/failure guidance is localized.
  • Score storage still delegates picker/path/PDF validation to the Rust-owned boundary; JS only localizes fixed safe failures.

Current verification state

The exact-head repository workflows for 537d2674a400c3d854102ad01606565c0cf022d7 have been dispatched but are currently queued. No predecessor-head test, coverage, security, release, or review evidence is being counted as success for this head.

Before merge this head still requires the repository ruleset’s current exact-head CI/security/SAST/SBOM/build/release/coverage/review gates and a qualifying independent non-author approval.

Dependency and supply chain

  • No direct dependency or lockfile change in this PR.

Reviewer checklist

  • Target branch remains develop.
  • Branch protection and required gates were not weakened.
  • Unknown implementation errors remain redacted/fail-closed.
  • Exact current-head deterministic workflows are terminal-success.
  • Exact current-head required review gates are terminal-success.
  • Qualifying independent non-author last-push approval is present.

… add next-action guidance

- Remove internal-boundary jargon from user-visible strings (bridge,
  transcription/separation pipeline wording, raw enum values in tooltips
  and metric cards).
- Give every error and pending state a next action (retry / re-pick file /
  check link), keeping regex-compatible prefixes for existing flows.
- Localize previously hardcoded English UI strings in Workspace stem
  player, export buttons, song timeline summary, and GrooveMap states;
  add matching keys to ko/en locales (key sets stay in sync).
- Display-only change: no behavior, permission, IPC, or storage changes.

Verified: tsc --noEmit clean; vitest 196/196 with 100% statements,
branches, functions, and lines coverage.
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 10 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ec8d3e17-8be9-471e-9723-8e2a70b142d4

📥 Commits

Reviewing files that changed from the base of the PR and between 7ad56cf and ad82ef9.

📒 Files selected for processing (19)
  • apps/desktop/src/App.localized-source-error.test.tsx
  • apps/desktop/src/App.test.tsx
  • apps/desktop/src/App.tsx
  • apps/desktop/src/App.youtube-url-guidance.test.tsx
  • apps/desktop/src/features/score/ScoreView.test.tsx
  • apps/desktop/src/features/score/scoreStorage.localized-errors.test.ts
  • apps/desktop/src/features/score/scoreStorage.ts
  • apps/desktop/src/features/workspace/ConfidenceBadge.tsx
  • apps/desktop/src/features/workspace/GrooveMap.tsx
  • apps/desktop/src/features/workspace/Workspace.test.tsx
  • apps/desktop/src/features/workspace/Workspace.timeline-summary.test.tsx
  • apps/desktop/src/features/workspace/Workspace.tsx
  • apps/desktop/src/i18n/index.test.ts
  • apps/desktop/src/lib/analysis.localized-errors.test.ts
  • apps/desktop/src/lib/analysis.test.ts
  • apps/desktop/src/lib/analysis.ts
  • apps/desktop/src/lib/analysis.youtube-import-guidance.test.ts
  • apps/desktop/src/locales/en/common.json
  • apps/desktop/src/locales/ko/common.json

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.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Collaborator Author

@opencode-agent Exact-head owner repair on existing branch fix/customer-copy-next-action@5ee3736724235c7e0a882e2de8822be97f71381d, protected develop@7ad56cf0065d068ec6463d92726de4855a6e201d. Refetch head/base/target blobs/review threads immediately before writing; do not create a competing PR or overwrite intervening movement.

Apply receiving-code-review + test-driven-development + verification-before-completion to the two current review findings, and only resolve a thread after its successor exact head proves the corresponding contract.

  1. Validated buyer-visible bug — singular timeline grammar (PRRT_kwDORjvEXs6cTuB7). Current English locale defines songTimelineSummary as "{count} sections mapped ...", and Workspace.tsx only replaces {count}. A one-section song therefore renders 1 sections mapped. Write the smallest realistic RED desktop regression with navigator.language = en-US and exactly one section; require 1 section mapped ... and reject the plural form. Fix at the i18n authority, not with English-specific string surgery in JSX: add explicit singular/plural timeline-summary keys (same key set in EN/KO; Korean may use equivalent text for both) or an equally typed locale-owned plural contract, then select by section count in Workspace. Preserve zero/many semantics and 100% owned branch/function/line/statement coverage.

  2. Validated mounted-surface localization gap — low-level score/analysis errors (PRRT_kwDORjvEXs6cTuDI). scoreStorage.ts currently throws fixed English BRIDGE_UNAVAILABLE_MESSAGE / INVALID_RESPONSE_MESSAGE; ScoreView can surface the raw first line via bridgeErrorDetail, so Korean users can receive English alerts. analysis.ts has analogous fixed buyer-facing fallback/guidance text. Do not make low-level storage/bridge code depend on React locale. First add RED Korean-locale UI regressions at the mounted surfaces proving invalid score response / bridge-unavailable and the PR-touched YouTube/browser fallback paths render Korean next-action copy without leaking internal bridge terms. Then repair at the UI/error-classification boundary: retain stable machine/error classification or known safe details underneath, but map known stable failure classes/messages to typed locale keys before display; unknown details must continue through existing redaction/fail-closed behavior. Keep EN behavior and SAFE_LOCAL_AUDIO_MESSAGES byte contract intact.

Run focused desktop tests first, then full desktop Vitest + exact 100% statements/branches/functions/lines, TypeScript, i18n key-parity test, and canonical quickcheck/required exact-head workflows. Do not weaken gates, alter unrelated dependencies, or claim success from predecessor/queued/rate-limited review evidence.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 0 new potential issues.

Open in Devin Review

Copy link
Copy Markdown
Collaborator Author

@opencode-agent Continue only on existing fix/customer-copy-next-action from exact head a37583aa7cf0507de6f90357fad49abc4599f32d, protected develop@7ad56cf0065d068ec6463d92726de4855a6e201d. Refetch head/base/target blobs/reviews/checks immediately before writing and adapt if moved; do not create another branch/PR or overwrite concurrent movement.

Apply systematic-debugging + TDD + verification-before-completion to the remaining deterministic desktop failures from predecessor exact release job 32926490181/98050367555 (merge-result checkout eaab5a3e20ed45fc8792f7ffef4be834256f3bec). I already fixed the separate DOMMatrix is not defined suite-import defect at successor head a37583a by giving App.localized-source-error.test.tsx the same pdfjs boundary mock as canonical App.test.tsx; do not duplicate or replace that repair.

The remaining first-causal contracts are current-source-verifiable test drift, not permission to roll back the buyer-safety localization:

  1. ScoreView.test.tsx mocks ../../i18n with an incomplete dictionary. scoreStorage.ts now correctly resolves scoreDesktopOnly and scoreInvalidResponse through the locale authority, so the mock returns literal keys. Extend only the test mock with the exact current English locale values (Score PDFs are only available in the desktop app. and The score could not be prepared. Try adding it again.); keep production localization in scoreStorage.ts unchanged. Re-run the malformed attach/read/remove and no-desktop-bridge tests.
  2. App.test.tsx still expects old raw bridge/import details. Current production analysis.ts intentionally allowlists/localizes known-safe local errors and redacts unknown YouTube/import failures. Update stale assertions—not production—to require current English next-action copy: local read failure The selected audio file could not be read. Choose the file again.; YouTube bridge rejection and generic thrown import exception both Failed to import YouTube URL. Check that it is a standard YouTube video link, then try again.. Explicitly assert raw This video is age restricted. / Network Error are absent so the privacy boundary cannot regress. Preserve aria-invalid/aria-describedby assertions.
  3. Run focused App.localized-source-error.test.tsx, relevant App.test.tsx, ScoreView.test.tsx, analysis.localized-errors.test.ts, scoreStorage.localized-errors.test.ts; then full desktop suite/typecheck/owned coverage and canonical ./scripts/harness/quickcheck.sh under repository-pinned tooling. Do not touch dependencies/lockfiles/workflows or foreign repositories, and do not weaken redaction/localization/security contracts.

Commit only to this canonical branch. Do not resolve review state or claim readiness until the unchanged successor head has terminal focused/full evidence.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread apps/desktop/src/lib/analysis.ts
Comment on lines +66 to +74
function localAudioSelectionMessage(error: unknown): string {
if (error instanceof Error) {
const messageKey = SAFE_LOCAL_AUDIO_MESSAGE_KEYS.get(error.message);
if (messageKey) {
return analysisMessage(messageKey);
}
}
return analysisMessage("unsupportedLocalAudio");
}

@devin-ai-integration devin-ai-integration Bot Aug 26, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Local-audio error mapping stays fail-closed

localAudioSelectionMessage maps the native allowlist strings to distinct localized keys and falls back to unsupportedLocalAudio for any unknown error, so raw bridge detail is never surfaced. This preserves the prior SAFE_LOCAL_AUDIO_MESSAGES behavior.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread apps/desktop/src/locales/en/common.json
Comment on lines +351 to 359
} catch {
return {
ok: false,
error: {
code: "invalid_request",
message
message: analysisMessage("youtubeImportFailed")
}
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Redacted YouTube error detail is intentional

importYoutubeUrl now discards the caught bridge message and returns youtubeImportFailed, hiding details like age restriction. This matches the PR's stated intent to hide implementation boundaries.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 0 new potential issues.

Open in Devin Review

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread apps/desktop/src/locales/en/common.json Outdated
Comment on lines +60 to +63
/** Resolve a buyer-visible analysis message in the currently selected locale. */
function analysisMessage(key: TranslationKey): string {
return createTranslator(detectPreferredLocale())(key);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Storage/analysis errors resolve locale at throw time

scoreMessage (features/score/scoreStorage.ts:22) and analysisMessage (lib/analysis.ts:61) re-resolve locale from navigator on each error, while UI components fix the translator once at mount via useMemo. navigator.language is stable within a session so output stays consistent; the window-undefined storage test still resolves English.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread apps/desktop/src/App.tsx

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Localized import-failure copy survives safeErrorDetail redaction

The bridge-failure branch wraps youtubeImportFailed in safeErrorDetail (App.tsx:461), capped at 220 chars. Both locale strings are shorter and contain no URL/path/secret patterns, so redaction and truncation leave them intact.

(Refers to this code)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread apps/desktop/src/App.tsx
Comment on lines 442 to 452
if (!normalizedUrl) {
setSelectionError(t("youtubeImportFailed"));
setSelectionError(t("youtubeLinkGuidance"));
setSelectionErrorSource("youtube");
return;
}

if (!isSupportedYoutubeUrl(normalizedUrl)) {
setSelectionError(t("youtubeImportFailed"));
setSelectionError(t("youtubeLinkGuidance"));
setSelectionErrorSource("youtube");
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Stale tests assert removed YouTube rejection copy

handleImportYoutube now shows youtubeLinkGuidance ("Use a standard YouTube video link...") for empty and unsupported URLs. Six existing tests still assert "Failed to import YouTube URL." (App.test.tsx:1204, 1216, 1228, 1240, 1253, 1266). That text no longer renders, so each getByText throws and the tests fail.

Prompt for agents
handleImportYoutube in apps/desktop/src/App.tsx now sets t("youtubeLinkGuidance") for the empty-URL branch and the unsupported-URL branch, replacing the old t("youtubeImportFailed") text. Six tests in apps/desktop/src/App.test.tsx still assert the old copy via screen.getByText(/Failed to import YouTube URL./i): 'rejects empty YouTube URL', 'rejects malformed YouTube URL', 'rejects non-http YouTube URL', 'rejects non-allowlisted YouTube URL intake before invoking the bridge', 'rejects downgraded YouTube URL intake before invoking the bridge', and 'rejects duplicate YouTube video parameters even when one is blank'. Update these assertions to expect the pre-import guidance copy ("Use a standard YouTube video link (youtube.com/watch or youtu.be).") so they match the new behavior, consistent with the new App.youtube-url-guidance.test.tsx.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 0 new potential issues.

Open in Devin Review

Copy link
Copy Markdown
Collaborator Author

@opencode-agent Exact-head owner repair on existing canonical branch fix/customer-copy-next-action@ad82ef90582aa4793df57da5eaf3f38e2896d029, with independently refreshed protected develop@749511c3ad4000090048718f685c6bee6b3d2c25. Refetch the branch head, live base, apps/desktop/src/App.test.tsx blob, review thread PRRT_kwDORjvEXs6cbq-B, and exact-head checks immediately before writing; if the branch moved, inspect and adapt rather than overwriting it. Do not create another PR.

Apply receiving-code-review, systematic-debugging, and verification-before-completion to the validated current finding: handleImportYoutube now deliberately uses youtubeLinkGuidance for client-side empty/unsupported URL rejection, but six existing tests in App.test.tsx still assert the removed Failed to import YouTube URL. copy. Update only those pre-import rejection assertions (rejects empty YouTube URL, malformed URL, non-http URL, non-allowlisted URL before bridge, downgraded URL before bridge, duplicate video parameters) to the current format-specific guidance Use a standard YouTube video link (youtube.com/watch or youtu.be). Preserve the separate post-admission bridge-error test that correctly expects Failed to import YouTube URL/availability guidance and verifies raw bridge detail redaction.

Run the smallest focused App test proving the six rejection cases and the post-admission failure case, then the repository-prescribed desktop/full verification that is feasible. Commit only the stale-test repair to this existing branch. Resolve PRRT_kwDORjvEXs6cbq-B only after the successor exact head is demonstrably green for that contract. Do not weaken/skip tests, alter production copy to satisfy stale expectations, touch dependencies/workflows, or claim merge readiness from queued/predecessor evidence.

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