← blog

My site now knows when I'm doomscrolling

A small status API, Apple Shortcuts, and a line under my name that stops guessing.

Under my name on the homepage sits a short line: what I am doing right now. For a while it guessed from a Mumbai schedule — sleeping at night, lunch after dhuhr, shipping the rest of the day. Cute. Often wrong.

I wanted the line to tell the truth. When I open X or Instagram, it should say I am doomscrolling. When I walk into the gym, it should say I am at the gym. When I close those apps, it should clear and fall back to the schedule.

What it does

One public read. One authenticated write. One row in Supabase. The homepage polls every fifteen seconds and prefers the live override while it is still fresh.

  • GET /api/status — public. Returns { active, code, message, expiresAt }.
  • POST /api/status — private. Bearer secret required. Sets or clears the override.
  • DELETE /api/status — private. Same as posting code=clear.

The body never carries free text for the homepage to print. Shortcuts sends a code. The server maps that code to a message and a default TTL in libs/status/catalogue.ts. Unknown codes are rejected. That stops junk from landing under my name.

The wire format

Use the www host. Apex wahabshaikh.com answers with a 308 to www. Silent app-open automations on iOS often hang on that redirect and die with “Automation took too long to run.”

Set doomscrolling (no JSON body — better for silent automations)
POST https://www.wahabshaikh.com/api/status?code=doomscrolling
Authorization: Bearer $STATUS_API_SECRET
Arrive at the gym
POST https://www.wahabshaikh.com/api/status?code=gym
Authorization: Bearer $STATUS_API_SECRET
Clear when the app closes (or when you are done)
POST https://www.wahabshaikh.com/api/status?code=clear
Authorization: Bearer $STATUS_API_SECRET

JSON still works for curl and manual tests: { "code": "doomscrolling", "ttlMinutes": 45 }. Form posts work too. Query ?code= is the path I use on the phone because there is nothing to misconfigure in the request body.

Codes and defaults

  • doomscrolling → is doomscrolling 📱 — 30 minutes
  • gym → is at the gym 🏋️ — 90 minutes
  • shipping → is shipping 💻 — 60 minutes
  • sleeping → is sleeping 😴 — 6 hours
  • praying → is praying 🤲 — 15 minutes
  • out → is out and about 🚶 — 2 hours
  • clear → drops the override; schedule takes over

Gym is not on the schedule anymore. The schedule only covers sleep, prayer, meals, naps, and shipping. If I am at the gym, Shortcuts says so. If I forget, the site does not invent it.

How the phone talks to it

In Shortcuts, make a Personal Automation: When App Opens → X (and another for Instagram). Add Get Contents of URL. Method POST. URL with ?code=doomscrolling. One header: Authorization = Bearer plus the secret. No body. Turn off “Ask Before Running” if you want it silent.

Add a second automation: When App Closes → same apps → POST ?code=clear. That is how the line stops claiming I am doomscrolling after I put the phone down.

For the gym, use Arrive at Location (or a Focus) and POST ?code=gym. Leave or a timer can clear it, or let the 90-minute TTL expire.

Why the write path is odd

App-open automations get a short time budget. Waiting on a cold serverless function plus a database write is enough to lose the race. So POST authenticates, validates the code, returns an optimistic JSON payload at once, and persists with Next.js after(). The homepage may take a beat to catch up; the Shortcut does not sit there timing out.

The function prefers Mumbai and Singapore regions so a phone in India is not round-tripping to Virginia for a two-field upsert.

Storage and trust

Table wahab_live_status has one row (id = 1). RLS allows public SELECT. Writes go only through the service role inside the route. The secret never lives in the query string — only in the Authorization header — so it stays out of proxy logs and Referer trails that query tokens love to leak into.

On read, the API ignores the stored message string and resolves copy from the catalogue by code. Change an emoji once; every active override picks it up without a rewrite.

What visitors see

WahabLiveStatus starts from the server when a live override is already known. If the cached page only has the schedule, the client holds an empty line until the first GET /api/status returns, then paints live or schedule. That stops the flash of “is probably shipping” before “is doomscrolling.”

No dashboard. No app. A bearer secret, a few automations, and a status line that keeps up.