A Chrome extension that counts the time you actually spend on X — not the time the tab sat open — and gets loud when you blow through your daily budget.
Clinical instrument UI: paper #eceff3, ink #0e1116, one blue #1b34d8,
and red #d80027 reserved for exactly one thing — the time you took past the
budget. Nothing decorative is red. Instrument Sans and Roboto Mono, bundled, no
webfont request. No rounded corners, no gradients, nothing that blinks.
Not on the Chrome Web Store. Four steps:
- Grab
xtime-<version>.zipfrom Releases and unzip it. - Open
chrome://extensions. - Turn on Developer mode (top right).
- Load unpacked → pick the unzipped folder.
Leave that folder where it is. Chrome loads the extension from the path you picked — move it or delete it and the extension goes with it. Put it somewhere you won't tidy up, not in Downloads.
Two things to know: Chrome will occasionally nag about developer-mode extensions on startup, and there are no auto-updates — new version means downloading it again and hitting the refresh icon on the card.
Set your daily limit in the popup. It defaults to 30 minutes.
The clock only advances when all of these are true:
| Signal | Check |
|---|---|
| Tab is the one you're looking at | document.visibilityState === "visible" |
| The window has OS focus | document.hasFocus() |
| You've touched something recently | pointer / key / scroll / touch within the idle grace (default 45s) |
The grace window is the important part. It's long enough to read a thread without your hands moving, short enough to stop the clock when you walk away.
Two deliberate exceptions:
- Video you chose to watch counts even with your hands off — but only if it's unmuted and on screen. Muted autoplay in the feed is the timeline playing at you, not you watching it, so it doesn't count.
- A single reported chunk is capped at 30s. Sleep the laptop mid-session and it can't donate three hours to today's total on wake.
Only one tab can satisfy focus + visibility at a time, so two X tabs never double-count. Writes are serialised in the service worker regardless.
Cross your limit and X is taken over by a full-screen card. It escalates:
| At | Tier |
|---|---|
| 1× | TIME'S UP |
| 1.5× | STILL HERE |
| 2× | DOUBLE. YOUR. LIMIT. |
| 3× | ENOUGH. |
Each tier fires once. Past 3× it comes back every 15 more active minutes. Buttons are dead for 4 seconds so you have to actually read it. Then there are exactly two doors: close the tab, or earn 5 more minutes.
Copy lives in src/roasts.js. Add lines there; nothing else needs to change.
Not a button — a gate. It pulls a fresh article on AI / agentic dev, makes you read it, then makes you write back what it said.
No backend, no model, no API key. The extension fetches a public RSS feed directly and does the rest with string work.
- Read it. Scroll to 85% and dwell for a length-scaled minimum (45s floor, 2.5 min ceiling). The dwell clock only runs while the window is focused.
- Write it back. 50 words minimum, typed. Paste is off.
It checks two things: that you wrote 50 words, and that you wrote them yourself. That is all.
It does not grade whether your recall is about the article. An earlier version did — scoring your answer against the article's distinctive terms by document frequency — and it kept rejecting honest recalls that happened to describe the piece in different nouns than the piece used. Being told you weren't paying attention when you were is worse than the cheat it prevented. The gate was never meant to mark you correct. It's meant to make you read something and put it into your own words before you get back on the timeline.
So the clipboard is the only thing left to defend against, and it's defended twice:
- The textarea refuses
pasteanddropoutright, and reverts any single edit that drops more than 24 characters in at once. Undo and redo still work. - The service worker re-checks by coverage, for anything that got around the page — devtools, another extension. More than 40% of your words sitting inside a 6-word run lifted verbatim from the article is a paste. One incidental overlap is fine: "the app that used to be called Codex" is simply how anyone would recall that sentence.
Measured against real articles (test/reading.test.mjs): three genuine ~60-word
recalls land at 0%, 0% and 11.7% coverage; every pasted paragraph lands at 100%.
This is a commitment device, not a security system — you are both the lock and the lockpick. The point is to cost more than the impulse is worth.
Seven feeds, all carrying full article text inline — no page scraping needed:
| Source | Focus |
|---|---|
| Simon Willison | Daily, practical, filtered to AI tags |
| Interconnects | Post-training, RL, model releases |
| Latent Space | AI engineering essays |
| Import AI | Weekly research roundup |
| Hamel Husain | Evals and LLM practice |
| Chip Huyen | AI engineering |
| Sebastian Raschka | Deep LLM tutorials |
Across all seven, ~59 articles are readable at any time. A pasted paragraph is rejected on all 59.
Summary-only feeds are useless here — there is nothing to recall — which rules out OpenAI news, the Google AI blog, Hugging Face and Eugene Yan. Anthropic publishes no RSS at all (every standard path 404s, and their engineering page advertises none), though Simon's AI tag tracks their releases closely.
Each feed is a named host in the manifest; there is no <all_urls>. Three
articles are kept warm in the background so the gate never stalls, and you won't
see the same piece twice in 40.
If no feed is reachable you get an offline card: close the tab, retry, or dismiss — dismissing grants no silence, so the roast returns.
chrome://extensions→ enable Developer mode- Load unpacked → select this folder
- Pin it. The toolbar badge shows today's minutes — wheat under the limit, terracotta over, red at 2×.
Click the badge → SET.
- Daily limit — default 30 min. That's active time, which is a lot more scrolling than it sounds.
- Idle grace — default 45s.
- Let the roast close the tab — on by default.
- Reset today — two taps, in case something counted that shouldn't have.
manifest.json MV3. storage + alarms + notifications, x.com/twitter.com only.
src/background.js Accounting, day rollover, tier logic, badge, notifications.
src/content.js Attention detection + the roast overlay (closed shadow DOM).
src/roasts.js Copy bank, shared by the worker and the popup.
src/reading.js Feed fetch, parse, distinctive-term scoring. No DOM, no model.
popup/ The dashboard.
assets/ Icons + the bundled Outfit face (no remote font fetch).
test/ Headless logic tests.
dev/ Browser previews + the icon generator. Not shipped code.
No analytics and no telemetry. The only network requests are GETs to the seven feeds above; nothing about you is ever sent anywhere. History is kept for 60 days and pruned.
node test/logic.test.mjs # 37 assertions over the accounting + roast logic
node test/reading.test.mjs # 32 over feed parsing + the recall gate (offline fixture)
node test/reading.test.mjs --live # ...plus one real fetch
python3 -m http.server 8931 # then open:
# /dev/preview.html?state=over|under|crit[&prefs=1] popup, canned data
# /dev/preview-roast.html[?fast=1][&visible=1] roast + reading gate, real content script
python3 dev/make-icons.py # regenerate icons from the X mark