fix(jobs): honor the chain-end reopen refusal on both surfaces #229

Merged
nalum merged 1 commit from fix/chain-end-refusal into main 2026-08-26 18:35:12 +00:00
Owner

Closes #176.

Most of §6.2 and §6.3 already shipped with the options sheet, so this began as
an audit rather than a build — a requirement-by-requirement pass over both
sections on both surfaces. The dialog's counting question and button, the ticked
call-out, the survivors sentence, the notice-not-a-snackbar, the completed
sheet's struck title and tappable successor: all present, all left alone.

The refusal to reopen was broken in two different ways, and the code for it
existed in both.

On Android, completeJob() called a generic failure helper instead of the
tickFailure formatter sitting beside it — so a household member trying to
reopen a repeating job saw the raw failed_precondition code rather than §6.3's
wording. Its domain check was also keyed on the bare RPC status, which
ErrSubTasksOpen and ErrParentDone share with ErrChainMovedOn, so an
unrelated refusal could have been labelled a chain-end. It now reads the actual
domain code.

On web, the honest wording only appeared when the successor's date could be
resolved locally; otherwise it fell through to a generic message still reading
"delete that one first" — which contradicts §6.2 outright, since deleting the
successor is not what a household member needs to hear. Both paths now name the
successor and offer it.

Also pinned: deleting a completed repeating job leaves its successor alone. That
was already true and structurally guaranteed — delete.go's cascade follows
parentUid and never spawnedNext — but nothing tested it. The new integration
spec could not be made to fail first, which is stated rather than dressed up;
so does the fact that one of the four UI tests passes against the old code,
because the old bug's failure mode was omission rather than over-matching.

No goldens moved. No affordance widened. The sweep does not apply — this is
error-formatting logic on an already-shipped screen.

🤖 Generated with Claude Code

Closes #176. Most of §6.2 and §6.3 already shipped with the options sheet, so this began as an audit rather than a build — a requirement-by-requirement pass over both sections on both surfaces. The dialog's counting question and button, the ticked call-out, the survivors sentence, the notice-not-a-snackbar, the completed sheet's struck title and tappable successor: all present, all left alone. **The refusal to reopen was broken in two different ways, and the code for it existed in both.** On **Android**, `completeJob()` called a generic failure helper instead of the `tickFailure` formatter sitting beside it — so a household member trying to reopen a repeating job saw the raw `failed_precondition` code rather than §6.3's wording. Its domain check was also keyed on the bare RPC status, which `ErrSubTasksOpen` and `ErrParentDone` share with `ErrChainMovedOn`, so an unrelated refusal could have been labelled a chain-end. It now reads the actual domain code. On **web**, the honest wording only appeared when the successor's date could be resolved locally; otherwise it fell through to a generic message still reading "delete that one first" — which contradicts §6.2 outright, since deleting the successor is not what a household member needs to hear. Both paths now name the successor and offer it. Also pinned: deleting a completed repeating job leaves its successor alone. That was already true and structurally guaranteed — `delete.go`'s cascade follows `parentUid` and never `spawnedNext` — but nothing tested it. The new integration spec **could not be made to fail first**, which is stated rather than dressed up; so does the fact that one of the four UI tests passes against the old code, because the old bug's failure mode was omission rather than over-matching. No goldens moved. No affordance widened. The sweep does not apply — this is error-formatting logic on an already-shipped screen. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(jobs): honor the chain-end reopen refusal on both surfaces
All checks were successful
check / commits (pull_request) Successful in 15s
check / go (pull_request) Successful in 3m1s
check / report (pull_request) Successful in 8s
check / web (pull_request) Successful in 4m43s
android / build (pull_request) Successful in 6m33s
android / report (pull_request) Successful in 3s
d8e7890c57
Issue #176's audit found §6.2 (delete) fully built and correct on
both surfaces, but §6.3's reopen-refusal half only looked finished.

Android's completeJob() built a tickFailure(e) formatter but never
called it — the tick button routed failures through the generic
act(tickFailed) helper instead, so CHAIN_MOVED_ON showed the raw
"failed_precondition" code rather than naming the successor. Its
detection also keyed off the bare RPC status, which sub-tasks-open
and the parent's frozen bundle share with CHAIN_MOVED_ON — an
unrelated refusal could have borrowed the "took over" wording had a
successor happened to be resolvable too.

Web's tickFailure got the wording right when a successor was
resolvable locally, but fell through to errorMessage's generic
DOMAIN_ERROR_MESSAGES entry otherwise — which still read "delete
that one first", stale copy from before this ticket's redesign that
contradicts §6.2 (a deleted completed job leaves its successor
alone) and law 7 (name what survives, never invite the wrong fix).

No client control currently drives complete=false on a job that
already has one (completed jobs offer no footer), so the tests
exercise the identical tickFailure/CHAIN_MOVED_ON seam through the
paths that ARE reachable: web's step-uncomplete and Android's
completeJob, with a synthetic ConnectError/ConnectException carrying
the domain code — the real trigger is a server-side edge no UI
button raises today. Also adds an integration test pinning that
deleting a completed job never touches the successor it spawned
(structurally already true; previously unguarded against regression).

Form-fragment sweep: not applicable — every change here is
error-formatting logic inside an already-shipped screen, no row,
form or picker markup touched on either surface.

Test report

Suite Tests Result Skipped
Unit 1435 ✅ pass 1
Integration 131 ✅ pass —

Coverage: 27.0%

Updated by the check workflow · commit 5c5718f4ae

<!-- ci-test-report --> ## Test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit | 1435 | ✅ pass | 1 | | Integration | 131 | ✅ pass | — | **Coverage:** 27.0% <sub>Updated by the check workflow · commit 5c5718f4ae3c28aa41c4410fb2b47b48e9b43652</sub>

Android test report

Suite Tests Result Skipped
Unit (debug) ❌ 1 failed

Coverage:

Updated by the android workflow · commit 5c5718f4ae

<!-- android-test-report --> ## Android test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit (debug) | | ❌ 1 failed | | **Coverage:** <sub>Updated by the android workflow · commit 5c5718f4ae3c28aa41c4410fb2b47b48e9b43652</sub>
nalum force-pushed fix/chain-end-refusal from d8e7890c57
All checks were successful
check / commits (pull_request) Successful in 15s
check / go (pull_request) Successful in 3m1s
check / report (pull_request) Successful in 8s
check / web (pull_request) Successful in 4m43s
android / build (pull_request) Successful in 6m33s
android / report (pull_request) Successful in 3s
to 5c5718f4ae
Some checks failed
check / commits (pull_request) Successful in 13s
check / go (pull_request) Successful in 2m43s
android / build (pull_request) Failing after 4m50s
check / web (pull_request) Successful in 4m28s
check / report (pull_request) Successful in 4s
android / report (pull_request) Successful in 5s
android / build (push) Has been cancelled
android / report (push) Has been cancelled
check / commits (push) Has been cancelled
check / go (push) Has been cancelled
check / report (push) Has been cancelled
check / web (push) Has been cancelled
tag / tag (push) Has been cancelled
2026-08-26 07:03:55 +00:00
Compare
nalum changed target branch from test/job-card-title-goldens to main 2026-08-26 18:35:05 +00:00
nalum merged commit 5c5718f4ae into main 2026-08-26 18:35:12 +00:00
nalum deleted branch fix/chain-end-refusal 2026-08-26 18:35:12 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eagraiclainne/app!229
No description provided.