feat: implement repeated-setting change detection with guided calibration flow

- Detect when user changes same config setting 3+ times within 24 hours
- Show non-intrusive help prompt with 'Help me tune this' button
- Guided calibration flow tests both directions:
  - False positive test: walk around room
  - Missed motion test: sit still
- Suggest optimal value based on diurnal baseline SNR and link health
- Apply suggested value button writes to /api/settings
- Track changes in localStorage (spaxel_setting_changes)

Acceptance:
- Help prompt fires after 3+ changes in 24h
- Calibration flow tests both directions
- Suggests value based on system data
- Apply button works

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-05-04 00:21:19 -04:00
parent 399f86e734
commit 9007f6ed55
8 changed files with 2480 additions and 4 deletions

View file

@ -0,0 +1,16 @@
{
"bead_id": "spaxel-dz5s",
"agent": "claude-code-glm-4.7",
"provider": "zai",
"model": "glm-4.7",
"exit_code": 0,
"outcome": "success",
"duration_ms": 420982,
"input_tokens": null,
"output_tokens": null,
"cost_usd": null,
"captured_at": "2026-05-04T04:14:31.791794684Z",
"trace_format": "claude_json",
"pruned": false,
"template_version": null
}

View file

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
78884f1fd1e717f871bc5a3868c74572a9fe74fe
a898e4fc0faef5957f595cddde9f59970010dc90

View file

@ -109,10 +109,19 @@ func (b *Bus) Publish(e Event) {
b.mu.RUnlock()
for _, fn := range subs {
go fn(e)
b.inFlight.Add(1)
go func(fn Subscriber) {
defer b.inFlight.Done()
fn(e)
}(fn)
}
}
// Wait blocks until all in-flight Publish goroutines have completed.
func (b *Bus) Wait() {
b.inFlight.Wait()
}
// PublishSync sends an event to all subscribers, blocking until all complete.
// Use this when ordering matters (e.g., tests).
func (b *Bus) PublishSync(e Event) {

View file

@ -216,8 +216,12 @@ func (s *Storage) Close() error {
close(s.done)
s.wg.Wait()
// Final flush to ensure all events are written
s.flush()
// Wait for in-flight async EventBus delivery goroutines to complete,
// then drain any events they enqueued after the flusher stopped.
eventbus.Default().Wait()
for len(s.queue) > 0 {
s.flush()
}
return nil
}

Binary file not shown.

Binary file not shown.