fix(trim): short-clip coerceIn crash + friendly 'clip too short' messaging#97
Open
stozo04 wants to merge 4 commits into
Open
fix(trim): short-clip coerceIn crash + friendly 'clip too short' messaging#97stozo04 wants to merge 4 commits into
stozo04 wants to merge 4 commits into
Conversation
…n MIN_TRIM_DURATION A sub-400ms capture (e.g. a 335ms tap-and-release) reaches the Trim screen with trimEndMs = durationMs and no minimum-duration floor. The first handle drag or TalkBack setProgress then called coerceIn with runtime-derived bounds — coerceIn(startMs + 400, 335) — which Kotlin rejects with IllegalArgumentException on an inverted range. Crashlytics 7169b499fee6554684dba69b1ae1a8f0, first seen v1.0.27. Fixes #95. - Extract the handle clamp math into pure, import-free TrimHandleMath.kt (same JVM-testable split as media/BoomerangSequence.kt) and use it at all four call sites (drag + accessibility, per handle). Each derived bound is clamped so the range is valid by construction; on a too-short clip both handles pin (start=0, end=duration), matching the already disabled NEXT button (trimValid) on the Trim screen. - Add TrimHandleMathTest: the exact crash values (335 vs 400), normal-clip behavior preservation, boundary durations, and a duration/target sweep asserting the clamp is total. Verified the suite fails with the exact production exception against the unguarded logic (5 failures) and passes 12/12 with the guard. - Add lesson 025 + index entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0131ua1xqjNHsg8Cs2sn7dkj
9 tasks
Play rollout for the issue #95 trim-handle crash fix (Crashlytics 7169b499). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0131ua1xqjNHsg8Cs2sn7dkj
… a silent dead-end Issue #95 follow-up. The clamp fix (Lesson 025) stopped the crash, but a sub-MIN_TRIM_DURATION clip still landed on the Trim screen with pinned handles and a no-op SAVE. Now both entry points gate at 400 ms and say so: - Import (onVideoPicked): reject before any copy (re-checked post-copy) with a "That clip's a bit short" dialog — the sibling of the existing "a bit long" dialog, same recreation-safe StateFlow pattern. - Capture (Finalize): a finalize under 400 ms, or ERROR_NO_VALID_DATA (tap-and-release, zero encoded frames), discards the scratch and shows a "hold the record button a little longer" snackbar on the viewfinder. - Clamp math + trimValid gate stay as the backstop. 5 new JVM tests (pre/post-copy reject, exact-400ms accepted, short finalize, ERROR_NO_VALID_DATA). Verified live on the Pixel 8 AVD — proof screenshots + report in docs/e2e/2026-07-06_111500-too-short-ux.md, and the PR #97 pre-merge E2E gate report in docs/e2e/2026-07-06_104410.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmDD5TDJxEyqhfa9f7jn8S
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
…e too-short snackbar "Hold the record button a little longer" implied press-and-hold recording, but the shutter toggles on tap (CameraScreen tap -> startBurstCapture, tap -> stopBurstCapture). Now: "That was quick! Record a little longer to make a loop." Re-verified live on the Pixel 8 AVD; proof screenshot in docs/e2e refreshed to show the corrected copy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmDD5TDJxEyqhfa9f7jn8S
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes the Crashlytics fatal
IllegalArgumentException: Cannot coerce value to an empty range: maximum 335 is less than minimum 400inFilmstripTrimSelector(Crashlytics7169b499fee6554684dba69b1ae1a8f0, first seen v1.0.27), and replaces the silent short-clip dead-end with friendly user messaging.Part 1 — crash fix (
fddfb26and earlier)Root cause (verified in source, not just from the auto-triage):
OpenLoopViewModel.kt:388) nor the gallery-import path (:567) floors a clip's duration before routing to Trim — both settrimEndMs = durationMsas-is. A tap-and-release capture of 335 ms lands on the Trim screen.FilmstripTrimSelector(TrimFilmstripControls.kt) clamped handle positions at four call sites (drag + accessibilitysetProgress, per handle) with bounds derived from runtime state, e.g.targetMs.coerceIn(curStartMs + minGapMs, durationMs)withminGapMs = 400. Kotlin'scoerceInthrows on an inverted range instead of clamping, so the first handle touch (or TalkBack interaction) crashed with exactly the reported message.Fix: extracted the clamp math into pure, import-free
ui/components/TrimHandleMath.kt(same JVM-testable split asmedia/BoomerangSequence.kt) and use it at all four call sites; each derived bound is clamped so the range is valid by construction. Added lesson 025. Release bump toversionCode 28/versionName 1.0.28.Note: supersedes draft PR #96 (same diagnosis, guard duplicated inline 4× with no tests), now closed.
Part 2 — "clip too short" UX (
6ed52f4, owner-requested follow-up)The clamp fix stopped the crash, but a sub-
MIN_TRIM_DURATION(400 ms) clip still landed on the Trim screen with pinned handles and a no-op SAVE — a confusing silent dead-end. Now both entry points gate at the existing legal minimum (400 ms, unchanged) and tell the user:onVideoPicked): a picked clip under 400 ms is rejected before any copy (re-checked post-copy, mirroring the too-long double-check) with a friendly "That clip's a bit short" dialog — the sibling of the existing "a bit long" dialog, using the same recreation-safeStateFlowpattern. The user stays on the Gallery.Finalizehandler): a capture that finalizes under 400 ms — or with zero encoded frames (ERROR_NO_VALID_DATA, a tap-and-release) — discards the scratch, returns to the viewfinder, and shows a "That was quick! Record a little longer to make a loop." snackbar instead of a silent return. (Copy corrected after owner review: the shutter is tap-to-start / tap-to-stop, not press-and-hold.)trimValidgate stay in place as the backstop for anything that slips through.Related Issue
Fixes #95
Type of Change
How Has This Been Tested?
Full local gate run on 2026-07-06 (this replaces the original remote session's "could not run Gradle" caveat — everything below is now actually verified):
:app:assembleDebugand:app:assembleRelease—BUILD SUCCESSFUL, exit 0.:app:testDebugUnitTestgreen, including the 12TrimHandleMathTesttests and 5 newOpenLoopViewModelTesttests (pre-copy import reject, post-copy import reject, exactly-400 ms accepted, sub-400 ms finalize discard +CaptureTooShortevent,ERROR_NO_VALID_DATA→CaptureTooShort).:app:lintDebug— 0 errors, no new warnings.IllegalArgumentException/FATAL lines. Report:docs/e2e/2026-07-06_104410.md.docs/e2e/2026-07-06_111500-too-short-ux.md.Manual QA checklist:
gradlew :app:assembleDebug :app:assembleRelease :app:testDebugUnitTest :app:lintDebug— all green (2026-07-06, local)TrimHandleMathcovered by the JVM sweep)What could not be verified: a sub-400 ms capture with valid data can't be produced on the emulator (CameraX finalizes
ERROR_NO_VALID_DATAunder ~700 ms there); that path is covered by the JVM finalize test withdurationOf = 335 ms. TalkBack readout of the new copy not exercised.Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_0131ua1xqjNHsg8Cs2sn7dkj
https://claude.ai/code/session_01MmDD5TDJxEyqhfa9f7jn8S