curfew-extension

Curfew: project doc (product spec & roadmap)

The working product document. The public landing page lives in README.md; the deep technical design lives in TECHNICAL_DESIGN.md. Image paths below resolve from the repo root.

A privacy-first Chrome (Manifest V3) extension that tracks how long you spend on chosen sites and enforces a daily budget per domain. When the budget for the day is spent, the site is intercepted and replaced by a calm reminder page until the next day.

Working name: Curfew (alternatives if ever needed: Recess, Curb; see §9). Target order: use it yourself → prove the value → publish to the Chrome Web Store.

Screenshots

   
Popup dashboard Options
The wall Willpower challenge

Popup dashboard with budgets and passes · the settings · the wall · a Willpower Protection challenge.

1. Why

“Block the whole site” tools (blocklist approach) are too blunt: YouTube is needed for study, X for work. The problem is the infinite scroll, not the site. “Screen time” dashboards show you wasted 3 hours but don’t stop you.

Curfew combines both sides: visibility (what actually took time) and enforcement (a hard wall after the budget is spent), for the sites YOU choose, with budgets YOU set.

2. Core idea

The metaphor is a curfew: the site is open during the day, and at a moment you choose it closes. The extension’s single job: meter time on configured patterns and make the site unreachable when the meter hits the budget.

3. Features (v1, the build target)

3.1 Quotas and budgets

3.2 Time tracking

3.3 Blocking

3.4 Daily-usage dashboard

The model is deliberately two-way: either you play by the rules (budget + passes), or you consciously opt out (disable the site or the whole app, both password-gated). No in-between “allow today” loophole.

3.5 Anti-circumvention posture (honest, not paranoid)

3.6 Export / import

4. Architecture (Chrome MV3)

Deep technical specification: full stack, module contracts, algorithms, DNR rule lifecycle, testing: docs/TECHNICAL_DESIGN.md. Where details differ, the tech doc wins; this section stays the summary.

curfew-extension/
  manifest.json
  docs/                      # technical design doc (see §4 intro)
  icons/                     (#16/32/48/128, later M3)
  _locales/                  # en, ru; strings only in UI
  src/
    service-worker.js        # event-driven core (see below)
    blocked.html/.js/.css    # the Curfew page
    popup.html/.js/.css      # the dashboard
    options.html/.js/.css    # settings + budgets editor
    common/
      time.js                # day key, minutes helpers (pure, testable)
      patterns.js            # pattern → URL match (pure, testable)
      budget.js              # open/closed decision, state machine (pure)
      storage.js             # typed storage wrapper + migrations
  tests/                     # node --test for common/*.js + invariants
  package.json               # no build step for v1: tests/runner only
  README.md

4.1 Service worker (no build, vanilla JS)

Event-driven only; no timers that must survive sleep (storage persists, SW restarts on events):

Event Action
tabs.onUpdated / onActivated + windows.onFocusChanged compute current tracked pattern. tab.url is visible ONLY for hosts the user granted (browser-enforced); the events themselves need no tabs permission
chrome.idle.onStateChanged (idle 60s) stop/start counting
chrome.alarms (every 5 min) flush usage; daily reset at local midnight; re-check the still-open tab: if it hit its budget while sitting on it → add block rule + redirect it (covers the “tab was already open” gap)
chrome.permissions (optional per-site host access) one prompt per site the user adds (see §4.3)
DNR dynamic rules budget exhausted → request-level redirect to blocked.html; rule removed on reset / “allow rest of day” (see §4.4)

4.2 Data model (storage.local, versioned under one key)

Orientation copy. The authoritative schema (per-pattern passes, session for SW-restart recovery, runtime for pass windows) is tech doc §6.1.

{
  "schema": 1,
  "config": {
    "masterEnabled": true,
    "graceSeconds": 10,
    "passMinutes": 15,
    "items": [
      { "id": "u1", "pattern": "*.reddit.com",
        "budgetMinutes": 30, "enabled": true }
      // pattern grammar: hostname with optional leading *.
      // No port/path in v1.
    ]
  },
  "usage": {
    "days": {
      "2026-09-02": {
        "patternSeconds": { "*.reddit.com": 772 },
        "passes": 1,
        "bySite": { "reddit.com": 772 }   // resolved site for dashboard
      }
    }
  },
  "settings": { "version": 1 }
}

Manifest permissions (all SILENT: the browser shows no install warnings):

"permissions": [
  "storage",                        // budgets + usage, local only
  "idle",                           // active-time detection (idle 60s)
  "alarms",                         // flush, midnight reset, open-tab re-check
  "declarativeNetRequestWithHostAccess",  // request-level redirects; no
                                          // implicit host access, no warning
  "contextMenus"                    // right-click "Add this site to Curfew"
],
"optional_host_permissions": ["*://*/*"]  // nothing granted at install

Deliberately NOT present (this is the positioning, verified against Chrome docs):

Per-site consent model, the only way access ever grows:

  1. User adds a site in popup/options → chrome.permissions.request (must come from an extension page with a user gesture, by design) for that site’s pattern, e.g. *://*.reddit.com/*;
  2. Grant = DNR rules for that site activate + tab.url becomes readable for that host (and only that host, browser-enforced);
  3. Deny = item stays in config marked “no access”, nothing tracked;
  4. Revoke = chrome://extensions → site access removed → rule removed, data for it can stay or be wiped (options toggle).

Note on chrome.tabs.update/reload/create: per the tabs API docs these need NO permission at all, so the fallback redirect (and the alarm re-check) require nothing extra.

Onboarding copy: “Curfew sees only the sites you add. Data never leaves your browser. Uninstall = gone.”

4.4 Blocking mechanics (decision: declarativeNetRequest dynamic rules)

5. Milestones

M What Acceptance
M0 Skeleton: manifest v3 (§4.3 permission set), popup w/ add-current-tab, options page w/ pattern+budget editor, local storage wrapper loads unpacked; add x.com → permission prompt → appears in both pages; no crashes
M1 Tracker + quota + interstitial 10 min on reddit → wall at budget-0; grace does not count; idle pauses counting; midnight reset; “add side note” not yet
M2 Dashboard (daily totals, per-site bars, pass counter) + export/import + usage pruning popup shows yesterday vs today; export→import round-trip
M3 Store package: icons, screenshots (3), privacy policy page, listing copy, review checklist (permissions, offstore repo, no tracking), publish appears in store, installs, works
M4+ Ideas: weekday-aware budgets; path rules; per-site “blocked until” button; optional sync via file; Firefox WebExtension port; stats export CSV  

v1 = M0..M2 as the personal product; M3 once you are happy.

6. Design principles (all future code reads against them)

  1. The user trusts the tool; the tool never trusts the user’s impulse. Rules are read from storage at decision time; the blocked page is served from the extension itself (chrome.runtime.getURL), never a remote page.
  2. No build step. Vanilla JS. Small codebase (~1.5-2k LOC total). It can be reviewed by a human in one sitting; 5 years later it still builds with nothing.
  3. Pure logic separate from chrome APIs (common/* has no chrome import): node --test covers time/day/pattern/budget decision; a DOM/db harness is not needed for v1.
  4. Honesty surfaces: the “stay anyway” button, the pass counter, the “extension can be disabled by design” note in the README. No fake-hard-to-get-around; the feedback loop is the product.
  5. New features only if they survive the “would I still use this in a year” test. No gamification, no streaks, no notifications: those become the distraction.
  6. No network layer, ever. No fetch, no XHR, no external scripts (default MV3 CSP blocks remote code anyway; keep it that way), no update polls, no error reporting. The only “bytes out” in the whole extension is the user-initiated export file.

7. Privacy policy (v1, to ship in store)

8. Competition / references (what was studied; what to improve)

9. Naming backstop

If curfew fails a name check (store / npm scope / GitHub):

10. Dev workflow