- Shipped
- August 14, 2026 at 5:59 PM UTC
- Author
- kamo
- Commit
- d098122
Voiding the punch a member is currently clocked in under already leaves them OFF server-side: RosterService.clockState reads the latest LIVE punch and skips voided rows, so no clock-out is written and none is needed. A SHIFT_OUT here would be worse than useless — the SHIFT_IN it would pair with was just voided, so it lands as an orphan and raises the missing-punch exception the editor exists to clear. What was actually wrong was the indicator. It polls on a 60s floor with no signal from the editor, so after deleting the entry they were clocked in under the member watched the chrome go on saying they were on the clock. The editor now announces a save that wrote something and the control re-reads. Fired from `finally` and gated on what LANDED rather than on success: a save that stopped half way still changed the record, and that is the case where a stale badge is hardest to notice. The event carries the edited memberId because the NavTop clock is self-scoped (fetchMyClockState takes no member id) while this same editor is used by HR against other members at /hr/attendance. shouldRefreshClockFor compares as STRINGS and refuses a number, for the reason parseClockState already gives: ids are unique_rowid() values past MAX_SAFE_INTEGER and two members can round to the same number. Does not fix the OTHER member's browser when HR edits their card — that needs the timecard.clock.{memberId} push relay, still pending.