An unsynced-work indicator on a disconnected client - why is it product behaviour a case must assert rather than a diagnostic?
answer
- The user's only window into undelivered work
- An output with a contract, not instrumentation
- Assert that it rises, holds and clears
- Clearing must follow confirmation, not sending
- Force a refusal and check it stays
basics
~20 sThe unsynced-work indicator is the only report a user gets about work that has not left the device, so it is a promise, not instrumentation. A case asserts it rises, holds while work is outstanding, and clears only on success.
solid answer
~50 sTreat it as an output with a contract. Assert that the count rises when an action is taken with no connection, that it names something the user can act on rather than just animating, that it falls to zero only after the work actually reached the far side, and that it does not clear when the attempt was refused. That last one is the assertion that earns the case: an indicator clearing the moment a request is sent tells the user their work is safe when it is not, and no crash, error or record-level assertion will reveal it. Assert the display at the same three points as the work itself - created, in flight, confirmed - and construct the negative by having the far side refuse the write, then require the indicator to still report outstanding work with an explanation the user can act on.
code
pseudocode · 18 linescase "outstanding-work indicator never clears on send":
sign_in()
link.disable()
ui.create_note("draft one")
ui.create_note("draft two")
assert ui.outstanding_count() == 2
far_side_stub.refuse_next_write(reason: "rejected")
link.enable()
await(until: delivery_attempt_started, deadline: 30s)
assert ui.outstanding_count() >= 1 # not cleared on send
detail = ui.outstanding_detail(0)
assert detail.names_the_item == true
assert detail.next_action_text != "" # user can act on it
ui.restart_application()
assert ui.outstanding_count() >= 1 # survives a restartgo deeper
Be ready to say what a user should be able to see while work has not left the device yet, and that a case checks that display the same way it checks anything else on the screen.
Explain the three points it must be asserted at - it appears, it holds while work is outstanding, it clears only once delivery is confirmed - and why a bare motion cue gives the case nothing to compare.
Show the negative case: make the far side refuse the write and require the indicator to keep reporting the work with something the user can act on, instead of clearing the moment a request was sent.
Own what the product promises here: how much detail a user is owed about undelivered work, when the application may discard it, and whether silence after a permanent refusal is ever acceptable.
On a device that keeps accepting work with no connection, there is a window in which the user's work exists in exactly one place - their handset - and nothing outside it knows. The only thing standing between the user and a wrong belief about that work is whatever the interface tells them: a count of items waiting, a per-item marker, a summary line saying when everything was last delivered. That display is not instrumentation for engineers. It is the product's answer to *is my work safe*, and it is asserted like any other output. ## Why the diagnostic framing loses Calling it a diagnostic leads to three bad outcomes, and all three show up in real suites: - It gets excluded from cases as noise, so nothing ever checks it. - It is allowed to be a spinner or a subtle tint, because "engineers can read the logs" - which makes it unassertable *and* useless to the user. - Worst, it is allowed to clear optimistically the moment a delivery attempt is sent, because clearing early looks better. The user closes the application believing the work is delivered. It is not. The third one is the case that justifies the leaf. Nothing crashes, nothing is logged as an error, and no assertion on the record itself catches it, because the record is fine - it simply is not where the user thinks it is. ## The three points to assert | Point in the flow | Expected display | The failure it catches | | --- | --- | --- | | Work created with no link | Count rises; the item is marked | Work silently accepted and forgotten | | Link restored, delivery running | Still reports outstanding work | Clearing on send rather than on success | | Delivery confirmed | Clears, and says when it happened | A marker that never goes away | Notice that the middle row is the only one requiring the case to hold the product in an inconvenient state, and it is the one that finds real defects. Getting there means controlling the far side: have a programmable stand-in refuse the write, or seed a record that will be rejected on arrival, so the attempt happens and does not succeed. ## Make it assertable, then assert the negative An indicator the case cannot read is an indicator the user cannot rely on either, so the two problems have the same fix. 1. **Prefer a countable state to a motion cue.** A number of outstanding items, or a per-item marker, can be compared with an expected value. A spinner cannot; the case can only observe that something is animating, which is true whether one item or forty are stuck. 2. **Assert rises, holds and clears** - all three transitions, in one case, so the display is proved to track reality rather than to appear at the right moment by coincidence. 3. **Force a failed delivery** and require the indicator to keep reporting the work, together with something the user can act on: which item, and what they can do about it. This is the assertion that stops optimistic clearing shipping. 4. **Assert the wording, not only the number**, for a small state the case constructs - a known count and one rejected item. Keep the expected text in the case rather than reading it from the same source the screen renders from, or the assertion compares a value with itself. 5. **Check the durable path.** If the product promises the work survives the application being closed, close and reopen it, and require the indicator to come back reporting the same outstanding items. ## Where the boundary sits This is a behavioural case, so it takes the display as a promise and proves it. It does not choose how often delivery is attempted, how the queue is stored, or what backoff is applied after a rejection - those live with the design of the mechanism. Nor is it the run's own logging: a case may certainly log the pending state to make failures diagnosable, but logging is read by an engineer after the fact, while the indicator is read by a user making a decision in the moment. Only one of the two is a promise, and only that one should fail the build when it lies. ## The judgement worth showing The interesting question is not whether to assert the indicator but what it is allowed to say. A count with no detail is honest but not actionable. A per-item marker is actionable but noisy on a long list. Silence after a permanent rejection is the only option that is never defensible, because it converts a recoverable situation - the user still has the text on screen and can retry or copy it - into invisible loss. Whatever the product chooses, the case pins it, so the next person who tunes the display for tidiness cannot quietly remove the only signal the user had.
- How is this indicator different from the run's own logging of what is still pending?The indicator is read by a user making a decision - whether to close the application, whether to retype the note, whether to believe a form was submitted. Logging is read by an engineer afterwards. A case may use both, but only one of them is a promise the product made, and only that one should fail the build when it says something untrue.
- How would you check its wording rather than only its count?Construct a small, defined state - a known number of items waiting and one that was refused - then assert the text a user sees names the outstanding item and the next action rather than showing a bare symbol. Keep the expected strings in the case rather than reading them from the same source the screen renders from, or the assertion is comparing a value with itself.
saying these in an interview costs you the question
- Calls the indicator a debugging aid, not behaviour
- Asserts that it appears but never that it clears
- Accepts clearing as soon as a request is sent
- Never forces a refused delivery attempt
- Settles for a spinner instead of a countable state
- Ignores what the user is told after a permanent refusal