Pingline — rubric and defend your code
Unit 26 · Pingline rubric. Score yourself honestly, then answer the defend-your-code prompts out loud. If you cannot defend a choice, that is the part to revisit before moving to Staybnb.
A messaging app is judged on how it behaves when the network misbehaves. Anyone can render a list of rows from a happy socket; the bar here is graceful degradation and correctness under concurrency. Grade the behavior, not the code you wish you had written.
Rubric
| Area | Meets the bar | Falls short |
|---|---|---|
| Live receive | A message from another client appears in the open thread with no refresh | Requires a pull-to-refresh or reopening the thread |
| Optimistic send | Own message shows instantly, then confirms or shows failed |
Waits for the server before showing; no failure state |
| Echo dedupe | Own message appears exactly once after the server echo | Message shows twice, or the optimistic row is orphaned |
| Connection actor | Status and outbox live in an actor; no data-race warnings | Mutable connection state in a class touched off-main |
| Reentrancy | Check-then-act sections are synchronous; no double-send under burst | guard/read, then await, then mutate on stale state |
| Offline buffer | Sends queue while down and flush once on reconnect | Sends silently lost while offline, or flushed twice |
| Ordering | Transcript sorts by createdAt, stable across reconnect |
Renders in arrival order; reorders on a replay |
| Groups | Group threads attribute each message to its sender (avatar + name) | One thread, no idea who said what |
| Reactions & attachments | Long-press tapbacks render as badges; image messages render an attachment bubble | No reactions; text-only messages |
| Read receipts | Delivered → Read shown under the last sent message only | No receipt, or a receipt under every message |
| Lifecycle | Stream unsubscribes when the thread closes | Leaks a live subscription per thread visit |
| Craft | Light/dark correct, VoiceOver labels, scroll-to-bottom | Dark mode broken, unlabeled controls |
Give yourself "meets the bar" only if you have seen the behavior with two clients and a toggled network, not merely written the code for it.
Defend your code
Answer these as if in review. The strong answers reference the exact mechanisms you built.
Why is connection state an actor?
Because its mutable state — connection status and the outbox — is written from several tasks
concurrently: the send path, the receive/reconnect path, and any status observers. An actor
serializes that access so no two tasks corrupt the buffer, without a hand-written lock. It is
not @Observable because it is infrastructure hit off the main actor, not view state.
What happens to optimistic messages if the send fails?
The optimistic row was inserted as sending and located by its client-generated id. On the
thrown error the send task flips that row to failed (found by id, not index), so the UI
shows a clearly failed message with a retry affordance. Nothing is silently dropped, and the
row is never mistaken for a delivered message.
Why does the server echo of your own message not create a duplicate?
Because the optimistic message and the echo share the same client-generated UUID. The
insert path dedupes by id: when the echo arrives, the row is recognized as already present
and dropped, leaving the single row you already showed.
Where is the reentrancy hazard in your outbox flush, and how did you close it?
The hazard is a guard/read on pending and the sequence counter, then an await on the
network send, then a mutation assuming the state is unchanged. I closed it by claiming the work
synchronously — removeFirst() and advancing the counter before the await — so no other
concurrent flush sees the same message or number.
What guarantees message order if Realtime delivers inserts out of order?
Every message carries a createdAt, and the view model sorts by it after each insert. Arrival
order and reconnect replays do not affect the rendered order because the timestamp is the sort
key, not insertion sequence.
Knowledge check
Q: An interviewer kills your network mid-conversation. What should they observe? Sends queue in the outbox and the UI reflects that (a pending/failed state or a banner); when the socket returns, the buffer flushes exactly once and the transcript reconciles. No lost sends, no duplicates.
Q: What is the single most important test to have for this project? A concurrency test that fires many sends/flushes at once and asserts no message is duplicated or dropped — the outbox-under-burst test that mirrors the reentrancy checkpoint. It is the one that catches the bug that never shows up single-threaded.