Repository navigation
VI-5068 Stop reporting failures as successes - #27
Merged
Merged
Conversation
Five places told the user something untrue when a request failed. Both flow delete modals raised their success alerts before awaiting the deletes, and awaited them with no catch. A rejection therefore arrived after the user had already been told the delete was under way, and skipped the rest of the handler, so the button kept spinning and the modal never closed -- with nothing on screen to say why. The webhook delete modal swallowed its error under the comment "Alert emitted by useApi", which useApi does not do: it has no reference to the alerts store and simply throws, so a failed delete was entirely silent and the modal closed as though it had worked. All three now settle first and report per item, through one useOutcomeAlerts helper rather than three copies of the alert-building. resolveVideoSourceId answered a failed collection request with source.id. That is a real answer for a source with no collection, so the caller could not tell it apart from a resolved one, and went on to show another flow's fps, duration and status as if they were this source's. It returns null now and the source is left for the next pass. The two remaining catches in the embed loops logged nothing, which is how a query parameter rejected on every poll went unnoticed for four months. They warn now. The delete-segments timerange was interpolated into the query unencoded, though a timerange is typed by the user and carries `[`, `:` and `)`. It goes through buildQuery like every other query in the app. Verified against a stub TAMS 8.2 store in headless Chrome: the embed still resolves a multi-source recording and reaches Open and LIVE 5s in, and a failing collection request now names itself in the console instead of resolving to the wrong flow. 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.
Ticket
VI-5068 — [tamstool] Upgrade TAMS Store Browser to TAMS 8.2
Follow-up to #26. That PR removed a query parameter an 8.2 store rejects; this one fixes the reason nobody noticed for four months, plus the four siblings of the same problem found in a sweep.
Findings
A sweep of every query the app builds and every
catchit holds turned up five places where a failure is reported as a success — and one encoding hole.1. Both flow delete modals announce success before the delete happens
FlowDeleteModal.jsx,FlowDeleteTimeRangeModal.jsx:delis SWR'striggerwith the defaultthrowOnError, so a failed DELETE rejects. By then the user has been told it worked; the rejection then skips the rest of the handler, leaving the button spinning and the modal open with nothing to explain it.2. The webhook delete modal swallows its error under a false comment
useApiemits no alerts — it holds no reference to the alerts store and simply throws. A failed webhook delete was therefore completely silent, andfinallyclosed the modal as though it had succeeded.3. A failed lookup answered with a plausible wrong id
Embed/index.jsx,resolveVideoSourceId:source.idis a legitimate answer for a source with no collection, so the caller cannot tell a resolved source from a failed one. It then reads another flow's fps, duration and status and renders them as this source's own. A confident wrong answer, on any flaky network.4. Two catches that logged nothing
catch { /* skip */ }in both embed loops. This is how&_t=…— rejected with a 400 on every 5s poll since April — stayed invisible until it was read off a console by hand.5. An unencoded, user-typed query value
A timerange is typed into a text field and carries
[,:and). Correct is%5B0%3A0_10%3A0). Every other query in the app goes throughbuildQuery/URLSearchParams; this one did not — on the destructive endpoint, against a store strict enough to 400 on an unknown parameter.The fixes
1–2. All three delete modals settle first and report per item, via one
useOutcomeAlertshelper in the alerts store rather than three copies of the alert-building. It returns whether everything succeeded; the timerange modal uses that to keep the typed timerange for a retry.3. Returns
null, and the caller leaves the source unresolved for the next pass.4. Both catches
console.warnwith the source or flow id and the error.5. Goes through
buildQuery.What the sweep did not find
Every query parameter the app sends is declared in the 8.2 spec. I extracted them from
bbc/tams@8.2and diffed:/sourceslimit,collected_by_ids,sort_by,reverse_order/flowslimit,source_id,collected_by_ids,sort_by,reverse_order,status/flows/{id}include_timerange/flows/{id}/segmentsaccept_get_urls,reverse_order,include_object_timerange,timerange,limit,storage_backend_tag{,_exists}.{name}DELETE /flows/{id}/segmentstimerangeBoth
sort_byenums match the spec's two different lists, andstatusis only ever sent to/flows, which is the only endpoint that declares it._twas the sole offender, and it escaped #20's spec review because it was hand-written into a template literal while everything reviewed went throughbuildQuery.Testing
Against a stub TAMS 8.2 store driven through the real app in headless Chrome:
npm run buildpasses (Node 22, same as CI)npm run lintstill cannot run in this repo (ESLint 9 needs a flat config, deliberately left out of VI-5068 Support TAMS 8.2 in the TAMS Store Browser #20) — the build is the gateThe delete-modal paths are argued from code rather than exercised: reproducing them needs a store that rejects a DELETE, which the stub does not model. The reasoning is in the finding above, and the shape now matches the tag and webhook-register modals, which already reported errors correctly.
One deliberate trade-off: against a store that fails every source, the embed will now warn once per source per 5s poll. Noisy — but that noise is the point, and it is what four months of silence cost.
🤖 Generated with Claude Code