Skip to content

No way to reject a falsely detected sleep: afternoon desk work logged as a nap #248

Description

@rube-de

What happened

Sat at my desk working through the afternoon, barely moving my wrist. Edge logged the stretch as sleep. I was awake the whole time.

The problem

There is no way to tell the app it got it wrong. Every affordance in the sleep-override path assumes a real sleep happened and only the timing is off:

  • "Sleep times look off? Fix them" / Edit → two time pickers, writes a sleep_override row (lib/ui/sleep/sleep_detail_screen.dart:144, lib/state/app_state.dart:1073)
  • Looks right → promotes auto_fallback to confirmed (lib/state/app_state.dart:1094)
  • Use auto → drops the override, back to auto (lib/state/app_state.dart:1110)

None of them can express "this window is not sleep at all". You can't zero the window out either, because the setter rejects it:

if (offsetSec <= onsetSec) return;   // lib/state/app_state.dart:1081

For a nap there's nothing at all. SleepPeriodsScreen is a read-only render of the naps block: no per-period edit, no delete, no confidence threshold anywhere in settings. The detector fires on runs of ≥20 contiguous still on-wrist minutes outside the main window (lib/compute/derivation_engine.dart:2813, :2843), which is a fair description of desk work: still wrist, low HR, hours long. It'll keep making the same call every weekday afternoon.

The only recourse today is Profile → Data history → Delete selected days, which wipes the whole day: raw 1 Hz, RR, samples, events, sessions, day_result, metric series, journal (lib/data/db.dart:2633). Throwing away a day of strain and heart data to get rid of a phantom 90-minute nap isn't a fix.

One more thing while this is open: deleteDays doesn't clear the sleep_override table, so an override set before a day-delete survives it and force-derives that day again from the stale window.

What I'd expect

A "Not sleep" action right where the app already asks whether it got the night right: the "Estimated from your heart rate" banner (lib/ui/sleep/sleep_detail_screen.dart:471). It already offers Looks right and Edit, and the missing third answer is "that wasn't sleep". Same action on the auto-detected footer (:529), and on each period in SleepPeriodsScreen so a single bad nap can go without touching the real night.

It should:

  1. mark the window rejected without touching raw data
  2. re-derive the day with that window excluded, so nap_min drops and strain/recovery recompute
  3. be reversible, same as Use auto

Implementation sketch, take it or leave it. The sleep_override table already carries a source column, and the engine already force-derives an override day even when it's finalized or locked (lib/compute/derivation_engine.dart:552, :836). A source = 'rejected' row, or a sibling table holding the rejected window, would reuse that path. The engine would skip the window instead of deriving from it.

Feeding rejections back as negative signal to the nap detector would be the obvious follow-up, but that's a separate ask.

Version

main @ 1f47b32

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions