TT-7583 fix: only warn of a network issue when the upload never completed - #515
Draft
nabalone wants to merge 7 commits into
Draft
TT-7583 fix: only warn of a network issue when the upload never completed#515nabalone wants to merge 7 commits into
nabalone wants to merge 7 commits into
Conversation
…eted A failed upload item told its callback nothing but success: false, so both itemComplete handlers responded to every failure by calling setOrbitRetries -- raising a "possible network issue" retry banner for a file we rejected locally or a 4xx the server deliberately returned. Give the callback a reason instead. UploadFailureReason names why the item failed (unsupported type, too big, local write, 4xx, 5xx, timeout, no response); suggestsConnectionProblem is the single place that decides which of those is evidence about the user's connection, and only a request that never completed qualifies. A status number alone could not carry this: local rejections never reach the network and have no status, and undefined already means "no response" to isRetryableUploadStatus. This narrows the warning: 5xx and 429 no longer raise it, since both mean we reached the server. Retry policy is unchanged -- isRetryableUploadStatus still governs the retry loop, and is now separate from the connectivity question. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ttings PhraseBackTranslateStepSettings seeded its artifact type from getTypeId, which returns the local Orbit record id. That value was emitted into the step settings JSON and persisted verbatim, but every reader decodes settings.artifactTypeId with remoteIdGuid (and SelectArtifactType hands the other step-settings dialogs remote ids). The `?? id` fallback in those readers hid it locally; once the step synced, peers could not resolve the GUID. Translate the local id to its remote id when seeding, keeping the local id when there is no mapping (offline-only artifact types have no remote id). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…er completed" This reverts commit a05f6b1.
…cases - useWavesurferRegions: a too-short first gap indexed result[-1] and threw; skip it so the gap joins the next region instead. - useWavesurferRegions: don't drop the only region when the whole clip is shorter than the minimum length. - useGuidedPhraseSegments: note the remaining case where auto-segment legitimately yields nothing and the bootstrap poll keeps spinning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eted A failed upload item told its callback nothing but success: false, so both itemComplete handlers responded to every failure by calling setOrbitRetries -- raising a "possible network issue" retry banner for a file we rejected locally or a 4xx the server deliberately returned. Give the callback a reason instead. UploadFailureReason names why the item failed (unsupported type, too big, local write, 4xx, 5xx, timeout, no response); suggestsConnectionProblem is the single place that decides which of those is evidence about the user's connection, and only a request that never completed qualifies. A status number alone could not carry this: local rejections never reach the network and have no status, and undefined already means "no response" to isRetryableUploadStatus. This narrows the warning: 5xx and 429 no longer raise it, since both mean we reached the server. Retry policy is unchanged -- isRetryableUploadStatus still governs the retry loop, and is now separate from the connectivity question. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
uploadFile's onerror coerced a dropped connection into a fake 500: httpStatus is 0 when the browser never got a response, and `httpStatus || 500` turned that into a status that claims we reached the server. The PUT loop then passed 500 down, so the preceding commit's classifier read it as ServerError and stayed silent -- for the single case the warning exists to report. Reject with statusNum 0 instead and leave httpStatus undefined, so a genuine connection drop classifies as NoResponse. Retry behavior is unchanged: uploadErrorHttpStatus ignores an out-of-range statusNum and falls through to isRetryableUploadStatus(undefined), which is still retryable. The user-facing message now renders "(no response)" for 0 as well as undefined. Add actions.failureReason.test.ts, which drives real XMLHttpRequest and Axios stubs end to end and asserts the reason that actually reaches cb for all seven paths. The unit tests on the classifier could not have caught this -- the signal was being destroyed a layer below where they read it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
REVERT THIS COMMIT BEFORE MARKING THE PR READY. It exists only so the connection-problem warning can be exercised by hand without arranging a real network fault, and it is deliberately isolated in one commit so dropping it is a single operation. From the browser console, no reload needed: localStorage.apmFakeUpload = 'put:0' PUT never reaches S3 -> NoResponse (warns) localStorage.apmFakeUpload = 'put:408' PUT times out -> Timeout (warns) localStorage.apmFakeUpload = 'put:403' S3 refuses -> Rejected (silent) localStorage.apmFakeUpload = 'put:500' S3 fails -> ServerError (silent) localStorage.apmFakeUpload = 'post:0' POST gets no response -> NoResponse (warns) localStorage.apmFakeUpload = 'post:403' API refuses -> Rejected (silent) localStorage.apmFakeUpload = 'post:500' API fails -> ServerError (silent) delete localStorage.apmFakeUpload back to normal Adds fakeUploadFailure.ts plus three blocks in actions.tsx, each marked TEMPORARY (TT-7583 manual test). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Follow-up to the TT-7583 auto-save work. The reported bug is that PBT auto-save shows
"Save Failed. Please check your internet connection and try again" when the connection is
fine. This PR fixes the half of that which is about misattributing failures to the network.
The problem
A failed upload item told its callback nothing but
success: false. BothitemCompletehandlers (
crud/useMediaUpload.ts,components/Uploader.tsx) therefore responded to everyfailure by calling
setOrbitRetries, which raises the "possible network issue" retry bannerfrom
Sources.tsx— including for a file we rejected locally or a 4xx the server deliberatelyreturned. The message blamed the user's connection for things that had nothing to do with it.
The change
UploadFailureReason(string enum,store/upload/uploadRetry.ts) names why an item failed:UnsupportedType/TooBig/LocalWriteFailedRejectedServerErrorTimeoutNoResponsesuggestsConnectionProblem()is the single place that decides, and bothitemCompletes nowcall it instead of duplicating the judgment.
Behavior narrows: 5xx and 429 no longer raise the connection warning — both mean we reached
the server. Retry policy is untouched:
isRetryableUploadStatusstill governs the retry loopand is now a separate question from "is the user's connection bad".
Why a reason code rather than a bool or a status number
Two alternatives were considered and rejected:
success(ok | retryable | terminal). Real appeal — it removes the redundantsuccess: false+ failure-object encoding. Rejected because a string union is truthy, so anymissed
if (success)(there is one atuseMediaUpload.ts) would silently start reportingsuccess; because "terminal failure" already means something different and specific in this file
(
onTerminalFailure= PUT exhausted, row deleted, file queued for later retry); and because itcan't carry
statusNum.isRetryableUploadStatus(undefined)returns true (at the retry layer "no status" means "noresponse"), and local rejections have no status at all — so an unsupported file type would
compute as a network problem, which is the exact bug being fixed.
The second commit is the interesting one
uploadFile'sonerrorcoerced a dropped connection into a fake 500 (httpStatus || 500;httpStatusis 0 when the browser never got a response). So a genuine connection drop on theS3 PUT classified as
ServerErrorand stayed silent — the one case the warning exists for.The signal was being destroyed a layer below where the classifier read it, so the unit tests on
the classifier could not have caught it. Found by building the fake-error harness, not by review.
actions.failureReason.test.tsnow drives realXMLHttpRequest/Axios stubs end-to-end andasserts the reason that actually reaches
cbfor all seven paths.6ecd5a1c(TT-7583 TEMPORARY: localStorage-driven fake upload failures). It isisolated in one commit precisely so this is a single operation. It adds
store/upload/fakeUploadFailure.tsplus three blocks inactions.tsxmarkedTEMPORARY (TT-7583 manual test).develop.git rebase origin/developconflicts incrud/useWavesurferRegions.tsx, betweena57c1342(auto-segment guard, inherited from thepart2 branch — not from this PR's work) and
a5ca8ff5(TT-7377, "don't reload blob whenediting"). Left unresolved deliberately: it's a judgment call about the auto-segment feature.
bc7d3b8e+ its revertba9802da,0b107780,a57c1342). If part2 lands separately, rebase to drop them.Manual test plan
Not yet run against the app — verified by unit/integration tests only. With the harness commit
still present, from the browser console (no reload needed):
Record and save a clause on Careful Speech or PBT for each. Two things to watch:
bannerrows and nothing else.pending-uploads queue, retry loop stopping. If anything beyond the banner changes with the
reason, that's a bug in this change.
The highest-value single check is
put:0vsput:500back to back — that pair is exactly thedistinction the fix turns on, and
put:0is the case commit 2 repairs.Two cases need no harness and exercise the
sendErrorpath instead of the network path: uploada file with an extension outside
.wav/.mp3/.m4a/.ogg/.webm/.pdf/.png/.jpg(UnsupportedType),and one over the size limit (
TooBig). Neither should raise the banner.Noticed, not addressed
The PUT retry loop (
actions.tsx) runs all 5 attempts even for a 403, unlike the POST loop whichbails on non-retryable statuses. Visible as a ~6s delay on
put:403. Pre-existing; out of scope.Verification
npm run typecheck:web, ESLint on changed files, and Prettier are clean; the 6 affected Jestsuites (58 tests) passed at the time the work was done. Per request, no CI/Devin checks were run
or waited on for this PR.
🤖 Generated with Claude Code