Let a day be left out of the stats

Every logged day was treated as equally trustworthy, and they aren't. A day
someone else had the puppy leaves a thin record that reads exactly like a real
one — five hours of sleep, two pees, no walk — and then drags down the average,
widens the longest gap in the Timing panel and puts a trough in every chart
that never happened. "Not counted", in the overview panel's heading, takes the
day you are looking at out of everything that aggregates across days.

Nothing is deleted or hidden. The day's own overview, history and sleep & wake
list are exactly as they were, dimmed and labelled; navigate to it and it is
all still there. Only the cross-day views stop seeing it, and weight and notes
keep counting wherever they fall — a weigh-in and a vet note are facts you
recorded, not behaviour a sparse logger distorts.

The mark is an ordinary event, the way a training session is. That was the
whole reason to do it this way: a set of marks that sync per-item with
last-write-wins and tombstones is exactly what the event contract already
provides, so un-marking is a delete, offline works, and two devices marking the
same day resolve themselves. An excluded_days table would have meant a table,
an endpoint, a request/response pair and a client cache to re-derive semantics
already in hand. Every renderer selects events by type, so a new type is inert
everywhere it isn't wanted; only the History log has to filter it out, being
the one view that shows whatever it is handed.

render() already computed the event list once and fanned it out, which made the
seam a single place: day-scoped panels keep the full list, weight and notes
keep it too, and the seven cross-day renderers take a counted one.

Filtering alone gets two things wrong, and those are most of the diff.

An empty slot lies. A marked day with no events draws a zero bar, which reads
as "the puppy barely slept" — precisely the misreading the mark exists to
prevent. So weeklyData zeroes the day's figures and flags it, and the four bar
charts, both actograms and the training grid paint a hatch in the slot instead.
Zeroing centrally rather than in each chart means every axis maximum, total and
tooltip downstream is already right. The slot stays: dropping it would make
consecutive bars stop being consecutive days.

Gaps balloon. gapsBetween subtracts consecutive events, so with a day's events
gone Tuesday's last pee sits next to Thursday's first and the subtraction
invents thirty hours — worse for the panel than the sparse day ever was. Any
gap whose interval touches a marked day is therefore discarded rather than
measured. Sleep and walk durations need no such care: sleepMsInRange and
walkMsInRange already clip to the day being measured, so a nap running in from
a marked day contributes only its counted part.

Both trend charts skip marked days explicitly rather than leaning on their
existing "any sleep at all" guard, which would have let a nap crossing midnight
give a marked day a non-zero total and sneak it back into the average.

Owner-only, alongside the rest of what a guest may not decide: a sitter should
not be able to rule their own thin day out, nor quietly take a good one out of
the averages. The server drops day-excluded events arriving on a guest session;
the client hides the control to match.
This commit is contained in:
Alexander Heldt
2026-09-07 19:01:06 +00:00
parent e22031ed4f
commit da68b733e4
7 changed files with 389 additions and 62 deletions
+35
View File
@@ -35,6 +35,10 @@ source-of-truth and sync between devices.
- All data is scoped to the signed-in account (see [Accounts](#accounts)): every
event, profile and photo carries a `user_id`, and `localStorage` is namespaced
per user so two accounts on one browser never mix.
- A day can be marked **not counted** (see [Days that don't
count](#days-that-dont-count)). The mark is itself an event
(`type: "day-excluded"`, timestamped at noon), so it syncs and un-marks by
tombstone like everything else.
A status pill in the header shows `syncing…` / `synced 2m ago` / `pending` /
`sync error` / `offline`. Tap it to force-sync.
@@ -100,6 +104,37 @@ events, profile and photos.
when you pass `-secure-cookies` (enable it behind a TLS proxy), so passwords
aren't sent in the clear.
## Days that don't count
Not every logged day is equally trustworthy. A day someone else had the puppy —
a sitter who forgets half the pees, a stay at kennels — leaves a thin record
that reads exactly like a real one, and then drags the averages down and puts a
misleading trough in every chart. **Not counted**, in the overview panel's
heading, takes the day you're looking at out of the aggregates.
- **Nothing is deleted or hidden.** The day's overview, history and sleep/wake
list are unchanged — just dimmed and labelled. Navigate to it and it is all
still there.
- **What stops counting** is the behaviour: sleep hours, timeline and trend,
walk minutes and patterns, pee/poo/meal counts, food, by-hour, the training
grid, and the Timing panel's typical gaps.
- **What keeps counting** is weight and notes. A weigh-in and a vet note are
records of fact, not behaviour a sparse logger distorts, so they stay on the
weight curve and in the Notes log.
- **Charts keep the day's slot**, drawn as a hatch rather than a bar. Dropping
it would make consecutive bars stop being consecutive days, and an empty bar
would read as "the puppy barely slept" — the exact misreading being fixed.
- **Gaps that reach across a marked day are discarded, not measured.** With the
day's events gone, Tuesday's last pee sits next to Thursday's first, and
subtracting invents a thirty-hour gap that would blow out the Timing panel's
"longest" far worse than the sparse day did. Sleep and walk durations need no
such care — `sleepMsInRange` / `walkMsInRange` already clip to the day being
measured, so a nap running in from a marked day contributes only its counted
part.
- **Owner-only.** A guest can't decide their own thin day shouldn't count, nor
take a good one out of the averages; the server drops `day-excluded` events
arriving on a guest session and the client hides the control.
## Guest links
A dog sitter needs to log a pee; they do not need your password. **Settings →