Case study

Chronos

Giving a silent research extension a voice: reminders and announcements that reach study participants, built inside a Manifest V3 browser extension.

Role
Software engineer, extension
Timeline
July 2026 – Present
Team
HATlab, Clemson University
Funding
NSF Award #2521037
TypeScriptVue 3WXTChrome MV3chrome.alarmsIndexedDBSupabaseVitest
01

Overview

Chronos is a companion browser extension for an NSF-funded human-factors study into secure-authentication usability. It quietly records auth events (logins, MFA prompts, logouts) as participants go about their normal browsing, and feeds the read-only analytics dashboard built in Project Sisyphus.

I joined the extension side of the project to debug and extend it, working alongside the team that built the detection engine.

02

The problem

A longitudinal study depends on people staying engaged, but Chronos was entirely passive. It collected data and said nothing. Researchers had no way to remind a participant to keep the extension running, and the broadcast messages admins could write in the dashboard had nowhere to land on the participant’s side.

Separately, the detector could not tell a phone number from a username, which muddied the data for phone-first sign-in flows.

03

What I built

  • Reminders

    A floating bell in the popup opens a rule editor. Participants and researchers can schedule a one-time reminder or a recurring one (daily, weekly, monthly, or every N minutes), toggle it on and off, and delete it. Everything is stored in the extension and fires as a native notification.

  • Announcements

    Admins broadcast messages from the Sisyphus dashboard. The extension polls for active announcements every 15 minutes and raises each one as a native notification, so a message reaches a participant even when no dashboard is open.

  • A popup that knows who is using it

    Participants get a view-only notifications panel. Only the researcher view can create or edit rules, so the study cannot be nudged by accident from the wrong side.

04

Design decisions

Local first

Reminders live in extension storage and never touch the network. They keep working offline and add no participant data to what the study already collects.

One alarm, not many timers

Manifest V3 service workers are suspended and restarted at will, so per-rule timers would be lost. A single one-minute alarm scans for due rules, which survives every restart.

Validate at the edge

A rule cannot be saved without a title and message, a future time (for one-time rules), or a valid time, weekday, or day of month (for recurring ones). Bad rules never reach storage, so the scheduler can trust what it reads.

Dedupe by cycle

Each announcement carries a cycle key, and the extension remembers the last key it showed per announcement. A repeating announcement appears once per cycle, not once per poll.

05

Difficulties & fixes

Scheduling looks simple until real clocks get involved. These are the problems that took the most care.

01

A late tick shifted the reminder time

Advancing a daily reminder from "now" means that if the tick fires a few minutes late, tomorrow’s reminder is a few minutes late too, and the error compounds each day.

FIX

Daily, weekly, and monthly rules advance from their own scheduled slot instead of the current time, so the time of day never drifts no matter how late a tick runs.

02

A reminder on the 31st vanished in short months

Adding one month to the 31st overflows into the next month, or skips February entirely, so monthly reminders fired on the wrong day or not at all.

FIX

Monthly recurrence clamps to the last day of the target month, so a rule on the 31st fires on Feb 28 or 29, and returns to the 31st when the month allows it.

03

Waking from sleep fired a burst of reminders

A custom-interval rule that lapsed while the laptop was asleep was still "due" for every missed interval, which risked a stack of notifications on wake.

FIX

Custom intervals advance from the moment they fire rather than from their old slot, so a lapsed rule fires once and resumes its normal cadence.

04

Announcements re-notified on every poll

Polling every 15 minutes with no memory of what had been shown would raise the same announcement again on every pass.

FIX

The extension stores the last cycle key shown per announcement and only notifies when it changes. A failed poll is caught and logged, so a network blip cannot take down the background worker.

05

Phone-number sign-ins were misread

Phone fields (type=tel, autocomplete=tel, or a "mobile number" placeholder) fell through to a generic username or unlabeled input, blurring what participants were actually entering.

FIX

The content script now classifies phone fields as their own entry type and treats them as valid identifiers, so phone-first sign-ins are recorded accurately without being mistaken for a username.

06

Testing

Time-based code is only trustworthy if the clock is under your control. Every scheduler function takes the current time as a parameter, which let me write over 700 lines of Vitest tests for the rule store, recurrence math, scheduler, announcement client, and popup composable, covering month-end clamping, late ticks, disabled rules, and one-shot rules that must disable themselves after firing.

NSF Award #2521037 ↗