The period graph, and a timecard you edit as a draft

Featurekamo-internal
Shipped
August 12, 2026 at 10:40 PM UTC
Author
kamo
Commit
8775e47

Replaces the one-day timeline on **************** with a whole pay period, and changes what editing one feels like. The pills carry no text. They used to carry their own times, and a label is 60px wide however long the thing it describes was — so a ten-minute break and a four-hour shift drew the same pill, and the one property a timeline has that a table does not (width means duration) was the property it threw away. Both times now live in a tooltip that names the start, the end and the span between them, and follows the edge being dragged with a readout that updates as it moves. Corners are 2.5px: enough to take the mechanical hardness off, not enough to round a five-minute pill into a lozenge. The hour header labels every hour, each turned a quarter turn counter-clockwise so all twenty-four fit. Four labels six hours apart cannot tell anyone whether a pill starts at 9 or at 10, which is the only question the header is there to answer now that the pills are unlabelled. Every day in the period gets a row, INCLUDING days that have not happened yet. A graph that only draws days with data cannot show a missing punch, and the missing punch is the most common thing wrong with a timecard: the gap is the finding, and a finding needs a row to appear on. Future days are hatched rather than merely dimmed — "not yet" and "nobody recorded anything" are different facts. Drags snap to the MINUTE. Five-minute snapping could not express 9:02, so a clock that was two minutes out could not be corrected and every corrected time acquired a roundness that read as an estimate. Rounding is a payroll policy the engine applies with neutrality reporting behind it; a slider must not apply a second, invisible one. Arrow keys nudge by one minute, Shift by fifteen, Alt takes one end on its own — a pointer cannot express a minute on a track where a minute is under a pixel. And editing is now a draft. Dragging used to open the mandatory-reason dialog on every release: fixing a day with three wrong punches meant writing three explanations, each a variant of "the clock was wrong", and a real protection under Anderson v. Mt. Clemens Pottery turned into a box people typed "correction" into to make it go away. A protection everyone learns to defeat protects nobody. Edits now stage freely and one explanation is collected on save — still mandatory, still recorded on every punch the save touches, but asked once, when somebody actually knows what they did. Nothing is written until then. Supporting changes: - app/lib/timecardGraph.ts holds every decision the graph makes — pairing, clamping, snapping, the DST-safe instant shift — as pure functions, because a decision about a wage record that can only be checked by dragging something with a mouse is a decision nobody checks. 34 tests. - fetchRange falls back to per-day reads on a 404 so the tab is not broken for whoever loads it between the SecurityService deploy and this one. - the add-a-time field round-trips through local wall-clock. It rendered a UTC slice into a datetime-local input and parsed the result back as local, so every visit to the field moved the time by the zone offset in silence.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing