Skip to content

feat(radar): replace the header ticker with a context-aware radar card - #146

Merged
itabajah merged 2 commits into
mainfrom
feat/radar-focus-card
Sep 3, 2026
Merged

feat(radar): replace the header ticker with a context-aware radar card#146
itabajah merged 2 commits into
mainfrom
feat/radar-focus-card

Conversation

@itabajah

@itabajah itabajah commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Why

The header ticker rotated a seeded, priority-weighted pool of one-liners every 9 seconds. That meant the single thing that actually mattered (an overdue assignment, a class starting in 8 minutes) was on screen about a third of the time, "class in 14 minutes" could be 14 minutes stale (items only rebuilt on 15-minute buckets), the facts were buried inside the jokes, and nothing could be acted on or dismissed.

What

Radar — a context-aware "what matters right now" card, built end to end.

Ideas weighed

  1. Focus card with deterministic ranking, pinned headline, live countdowns, inline actions and an up-next rail — chosen.
  2. Daily brief (counts summary) — informative but static.
  3. Mascot with moods — off-brand for the monochrome identity.
  4. Insight engine — folded into (1) as pacing / free-time hints.
  5. Today timeline strip — duplicates the calendar.

Design

  • domain/radar (pure, now injected, hash-only "randomness"):
    • live queue, ranked by a tiered score: class now / soon / next / tomorrow, homework by urgency (two picks, second prefers another course), the nearest exam with a study-pace hint ("5 unwatched · 1 lecture every 2 days"), a recordings backlog, setup nudges. Snoozed ids are filtered out.
    • calm rotation, only when nothing is live: late night (with the clock), weekend (Israeli week: Fri afternoon + Sat), morning, free day, "classes done for today", all-clear with semester progress stats, a daily course roast, study tips (the old NOTE tips).
    • Facts in a bidi-safe title + meta line (user text marked for <bdi> / isolate()); personality in a curated quip catalog (legacy lines kept, facts stripped, Hebrew flourishes kept), some quips conditional on context: free time before the next class, late-night.
  • services/storage/radarSnooze: device-local per-day snooze map (not synced, on purpose).
  • features/radar/RadarCard:
    • Live mode: top signal pinned; only the quip rotates. Chips in the "Up next" rail carry the one fact that matters (HW!! Wet 2 · 2 days overdue) and promote on click.
    • Mark done (homework) and Snooze until tomorrow, both with an undo toast; headline deep-links into the course dialog on the right tab/item.
    • Live class → red "now" dot + a thin progress bar that steps once a minute.
    • Rotation pauses while hidden / hovered / focused / reduced motion; countdowns still tick.
  • Removed domain/ticker, features/ticker, features/courses/tickerTarget; shared CheckIcon / BellOffIcon; README feature row + architecture text; a radar-in keyframe.

Tests

  • domain/radar.test.ts (53): every collector, ranking, snoozing, calm rotation, quips, hashing, formatting.
  • services/storage/radarSnooze.test.ts, features/radar/useRadarSnooze.test.tsx, features/radar/target.test.ts.
  • features/radar/RadarCard.test.tsx (13): setup nudge, pinned facts + bidi, done/undo, snooze/undo + persistence, chip promotion + overflow, deep-link, live progress, quip vs headline rotation, pause when hidden/hovered, reduced motion, minute ticks.
  • e2e/radar.spec.ts: setup → homework due today → done in place → snooze survives reload; deep-link to the Homework tab.

pnpm verify green (959 tests, coverage floors met: domain/radar 100% lines), pnpm e2e 13/13, screenshots checked in light/dark, desktop/mobile, live/calm.

🤖 Generated with Claude Code

itabajah and others added 2 commits September 3, 2026 12:53
The old ticker rotated a seeded, priority-weighted pool every 9 s, so the
one item that mattered was on screen a third of the time, countdowns went
stale for up to 15 minutes, facts were buried inside jokes, and nothing
could be acted on or dismissed. The radar replaces it end to end:

- domain/radar: deterministic collectors turn the semester + clock into a
  ranked `live` queue (class now/soon/next/tomorrow, homework by urgency,
  the nearest exam with a study-pace hint from unwatched recordings, a
  recordings backlog, setup nudges) and a `calm` rotation (late night,
  weekend, morning, free day, classes done, all-clear with progress stats,
  a course roast, study tips). Facts live in a bidi-safe title/meta line;
  personality lives in a curated quip catalog, some quips conditional on
  context (free time before the next class, late night).
- services/storage/radarSnooze: a device-local, per-day snooze map.
- features/radar/RadarCard: the top-ranked signal stays pinned (only the
  quip rotates), the rest wait in an "up next" rail whose chips carry the
  one fact that matters and can be promoted; homework can be marked done
  and any signal snoozed until tomorrow, both with undo; the headline
  deep-links into the course dialog; a live class shows its progress.
  Rotation pauses when hidden, hovered, focused or under reduced motion;
  countdowns still tick every minute.
- Removed domain/ticker, features/ticker and the tickerTarget mapper;
  shared Check / BellOff icons; README + comments updated.

Tests: domain (ranking, every collector, formatting, quips, stats, snooze
filtering), storage, hook, component, target mapping, and two Playwright
flows (setup -> homework -> done -> snooze survives reload; deep-link).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… cases

Review of #146 turned up six P2 issues and a handful of nits; all addressed:

- Snoozing a capped pick (top homework / nearest exam / biggest backlog /
  first undated) now reveals the next candidate instead of hiding the whole
  category: candidates are snooze-filtered before each collector caps.
- Class signal ids no longer embed the phase, so "until tomorrow" holds as a
  class moves next -> soon -> live; the headline re-enters on a kind change
  via a key that includes the kind.
- Keyboard activation of a chip / Done / Snooze that unmounts the focused
  control no longer drops focus to <body> (which also left rotation paused):
  focus is restored to the headline, hover and focus are tracked separately,
  and blur only releases when focus actually leaves the card.
- A zero-length slot (start === end, importer-only) is no longer a 24-hour
  LIVE class; it is not a class at all, matching the calendar's strict wrap.
- Ranking: the constants comment now describes the deliberate interleaving
  of the long-horizon tiers, and a class within the hour gets a proximity
  bonus so it is not stuck behind tomorrow's homework as a chip until 15 min.
- Snooze persistence moved to an effect with a try/catch (a throwing setItem
  keeps the snooze for the session), no ref written during render.
- Hebrew-only quips render inside <bdi>; `stats` dropped from the snapshot
  and the never-produced 'calm' meta tone removed; `hhmmToMinutes` shared
  from lib/dates by the calendar and the radar; "exams behind you" wording.

Tests added for each: snooze reveals the next candidate, class snooze across
phases, cross-tier ordering and the near-class bonus, zero-length slots,
Saturday-evening preview, day-rollover expiry through the card, keyboard
focus restoration, a throwing storage write, and a fixed-clock e2e that
seeds a running class and checks the progress bar and chip promotion.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@itabajah

itabajah commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

Review pass

A separate reviewer agent went through the diff line by line (verdict: merge after fixes, no P0/P1). Everything it raised is addressed in the follow-up commit:

# Finding Fix
1 Snoozing a class only hid its current phase (class_next: id) Phase-free class: ids; headline keyed on id + kind so phase changes still animate
2 Snoozing the top homework / nearest exam / biggest backlog hid the whole category Candidates are snooze-filtered before each collector caps, so the next one surfaces; snoozedCount counts what was actually hidden
3 Keyboard activation of a chip / Done / Snooze dropped focus to <body> and left rotation paused Focus restored to the headline when it leaves the card through an unmount; hover and focus tracked separately; blur releases only when focus really leaves
4 start === end slot became a 24-hour LIVE class Zero-length slots are not classes (matches the calendar's strict wrap)
5 Ranking comment claimed disjoint tiers; class 16–29 min away sat behind tomorrow's homework Comment now documents the intended interleave; a class within the hour gets a proximity bonus
6 saveSnoozes could throw from a click Persistence in an effect with try/catch; the snooze then lasts the session
nits Hebrew-only quip punctuation, ref written during render, unused stats/'calm' tone, duplicated toMinutes, "exams passed" wording All applied

Tests: 971 unit/component (was 959), coverage floors met (domain/radar 100 % lines), e2e 14/14 including a new fixed-clock flow (page.clock.setFixedTime) that seeds a running class and checks the progress bar and chip promotion.

@itabajah
itabajah merged commit 91faeb1 into main Sep 3, 2026
2 checks passed
@itabajah
itabajah deleted the feat/radar-focus-card branch September 3, 2026 10:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant