Files
waggle-os/docs/addiction-features/02-daily-brief.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

5.8 KiB
Raw Blame History

02 — Daily Brief Notification

Author: CC (Block B design pass) Date: 2026-05-01 Status: AWAITING_RATIFICATION Estimate: ~280 LOC + ~5-6h wall-clock Touches: apps/web (2 files), packages/server (1 route + 1 cron job), packages/core (1 generator), Tauri capabilities (notification permission)


User story

As a user, every morning I want a 1-2 sentence brief summarising what I discussed yesterday, what I decided, and what's worth revisiting today, delivered as either an in-app banner or a system notification — so I never lose track of work I did 24 hours ago.

Acceptance criteria

  1. At a configurable hour (default 09:00 local), a "Daily Brief" notification fires for the user.
  2. Brief content: 1-line summary of yesterday's activity (+ count of frames saved + decisions made), 1-line "today suggestion" derived from open tasks / pending follow-ups in workspace state.
  3. Two delivery channels:
    • In-app banner: top-right toast on first desktop mount of the day, dismissible.
    • System notification (Tauri): native Win/Mac notification with click-to-focus action. Opt-in per-platform.
  4. Configurable in Settings → Notifications: on/off toggle, hour-of-day picker, channel selection.
  5. Skips when user has no qualifying activity (yesterday frame count = 0); brief replaced with weekly digest pointer or suppressed entirely.
  6. One brief per day max — server-side dedupe on (user_id, date) so reopening the app doesn't re-fire.

UI sketch

In-app banner (top-right, 8s fade-out unless hovered):
  [Brain icon] Yesterday's brief
  You discussed: API rate limiting, Q3 roadmap. Decided: ship migrations
  Wednesday. Today's suggestion: review the auth-rewrite blocker.
                                                    [Read more]  [✕]

System notification (Tauri):
  Title: Waggle — Daily Brief
  Body:  Yesterday: 12 memories, 3 decisions. Today: review auth blocker.
  Action: Open app → focuses Chat / Memory tab

Data model

New table daily_briefs:

id INTEGER PRIMARY KEY,
brief_date TEXT NOT NULL UNIQUE,    -- 'YYYY-MM-DD'
content TEXT NOT NULL,              -- 1-2 sentence summary
yesterday_frame_count INTEGER,
yesterday_decision_count INTEGER,
today_suggestions TEXT,             -- JSON array
delivered_at TEXT,                  -- ISO timestamp; null until shown
delivered_via TEXT,                 -- 'banner' | 'system' | both
created_at TEXT NOT NULL

Settings additions (existing settings.json):

dailyBrief: {
  enabled: boolean,                 // default true
  hour: number,                     // default 9
  channels: { banner: bool, system: bool }   // default { banner: true, system: false }
}

Server-side generator

Cron job runs daily at configured hour (per-user; v1 single global hour) — generates brief by:

  1. Querying yesterday's frames (created_at >= startOfYesterday AND < startOfToday).
  2. Extracting decisions (importance=critical OR content matches "Decision X").
  3. Pulling open progress items / blockers from workspace-state.
  4. Sending to LLM with prompt: "Summarise in 1-2 sentences. Be terse. List top decision."
  5. Storing in daily_briefs table with delivered_at=null.

Client polls /api/daily-brief/today on desktop mount. If row exists with delivered_at=null, render banner + (if Tauri & user opted in) fire system notification, then POST /api/daily-brief/today/ack to set delivered_at.

Implementation notes

  • Cron infrastructure already exists (cronStore + CronScheduleLike in workspace-context.ts).
  • LLM cost: 1 call/day × ~500 tokens output ≈ $0.005/day on Sonnet. Acceptable.
  • Dedupe: server enforces UNIQUE(brief_date). Client never generates locally.
  • System notification: Tauri notification capability — already requestable; needs tauri.conf.json allowlist update.
  • Skip for fresh users: cron job pre-checks yesterday_frame_count > 0; if 0 and totalFrames=0 (cold start), skips entire brief.

Estimate

  • Server route (/api/daily-brief/today) + ack endpoint: ~60 LOC
  • Cron job + LLM generator: ~80 LOC
  • Settings tab additions: ~50 LOC
  • DailyBriefBanner component: ~60 LOC
  • Tauri notification wiring: ~30 LOC
  • Tests (cron logic, brief generation, dedupe): ~50 LOC
  • Total ~330 LOC including tests, ~5-6h with verification.

Risks + open questions

  1. Hour-picker timezone — server cron runs in server-local TZ; users on other timezones see briefs at wrong hour. v1: keep server-local; document. v2: per-user TZ.
  2. LLM cost — $0.005/day × N users = $0.15/user/month. Negligible for now but tracks.
  3. Empty days — first 7 days of new user have no yesterday. Either skip silently or use "Welcome" copy. Open question.
  4. System notification permission — Tauri requires explicit allowlist + user permission grant. v1 fallback to banner-only when permission denied.
  5. Generator failure — LLM down → no brief that day. Acceptable; user just sees yesterday's content next day. Don't retry within a day.
  6. Cross-device — user has multiple devices; brief generated server-side once, both devices show it. Already handled by server-side dedupe.

Out of scope (v1)

  • Multiple briefs per day (morning + evening) — wait for usage data.
  • Personalised tone / persona-flavored briefs — just a plain factual summary v1.
  • "Snooze brief" controls — just dismiss or off.
  • Email delivery — Slack-style integrations later.
  • Per-workspace briefs — global personal brief v1.

PM decisions needed

  • GO / MODIFY / SKIP
  • Default delivery channel(s) — banner, system, or both
  • Default hour (09:00 local? user-configurable on first run?)
  • Empty-day behavior (skip / welcome copy / encourage activity)
  • Brief tone (factual / encouraging / persona-flavored)
  • Generator model (Sonnet for cost / Haiku for speed)