{% raw %} # Online presence DevPlace shows whether a user is **online** right now. You see it on every profile page and at the top of a direct-message conversation, as a small status dot with a short label: `online`, `last seen 5m ago`, or `offline`. ## What you see - Every user **avatar** carries a small dot in its bottom-right corner: **green** when the person is online, **muted grey** when they are not, so presence is visible everywhere an avatar appears (the feed, comments, the navbar, message lists, and more). Hover the dot for the exact status. - On a **profile** and at the top of a **conversation** you also get the word `online`, or **`last seen ...`** with a relative time, or `offline` if the person has not been seen since the feature started tracking them. - The indicator **updates on its own** while you have the page open: if someone comes online while you are looking at their profile, the dot turns green within a few seconds, and it fades back to grey shortly after they go idle. You never need to refresh. - The **feed** shows an **Online now** panel at the bottom of the left sidebar: the avatars of everyone currently online, ordered alphabetically so they keep a stable spot, with a live count. People appear and disappear from it in real time as they come and go, again with no refresh. "Active" simply means loading any page on the site. There is nothing to switch on and no busy or away status to set; presence is automatic. ## How long you stay "online" You count as online for a short window after your last activity. The default window is **60 seconds**, so a moment after you stop browsing you quietly drop to `last seen ...`. Because the window is deliberately short, the status is honest: a green dot means the person really is here right now, not that they logged in hours ago. Operators can change the window, so on some deployments it may be longer or shorter. To keep the indicator steady rather than jittery, the status is **quick to turn on and slow to turn off**: you show as online the moment you are active, but a brief pause does not immediately drop you - there is a short grace margin before the dot goes grey. This stops the flicker you would otherwise see if your activity landed right on the edge of the window, and it applies to both the avatar dots and the Online now list, so they always agree. ## Privacy Online status is public, the same way your posts and profile are public. It reflects only activity on DevPlace and never your location, your device, or anything you do elsewhere. The only thing recorded is the time you were last active, and it is overwritten in place each time, never kept as a history. ## For contributors Presence is one timestamp plus a lightweight push, built to touch the database as little as possible. **The data.** Each user row carries a single `last_seen` column (UTC ISO). There is no separate table and no per-request insert, so the feature adds no data growth. A user is online when `now - last_seen` is under `config.PRESENCE_TIMEOUT_SECONDS` (env `DEVPLACE_PRESENCE_TIMEOUT_SECONDS`, default 60). **Writing it.** The `track_presence` middleware in `main.py` resolves the current user on each non-asset request and calls `services/presence.py` `touch(uid)`. `touch` keeps a per-worker in-memory throttle and writes `last_seen` at most once per `PRESENCE_WRITE_SECONDS` (half the window) per user, as an in-place `UPDATE`. Continuous browsing is a dictionary lookup, not a write. This is the only cross-worker-correct approach here: pub/sub is in-process, so SQLite is the shared medium. **Reading it.** `presence.is_online(user_row)` is exposed as the Jinja global `is_online(user)` and rendered from the user row a page already loaded, so no extra query runs. The value is also on `UserOut.last_seen` and `ProfileOut.profile_online` for JSON clients. **Live updates (change-only, hysteresis).** `PresenceRelayService` (`services/presence_relay.py`) runs on the service-lock owner and recomputes ONE global online set each tick that drives BOTH the avatar dots and the feed roster, so they can never disagree. Membership uses hysteresis (`presence.stays_online`): a user goes online at `PRESENCE_TIMEOUT_SECONDS` but only drops after an extra `PRESENCE_ONLINE_MARGIN_SECONDS` grace (`DEVPLACE_PRESENCE_ONLINE_MARGIN_SECONDS`, default 20) - quick on, slow off - which is what prevents boundary flicker. It pushes to `public.presence.{uid}` **only when a watched user's online bool actually changes** (or a new subscriber first appears) - never on a fixed interval - reading watched users in **one batched query per tick**, so an idle page costs zero messages. The frontend `PresenceManager` (`static/js/PresenceManager.js`, `app.presence`) subscribes any element carrying `data-presence-uid` (deduped per uid) and treats each frame's `online` flag as **authoritative** - it does not expire a live dot from its own clock, so an active user never flickers to grey. The staleness timer remains only as a fallback for elements with no live subscription (e.g. guests). Reuse `is_online`, `presence.stays_online`, the `public.presence.{uid}` topic, and `PresenceManager` for any new online indicator rather than re-implementing presence. **Online-now roster.** The same relay maintains one shared topic, `public.presence.roster`, republished **only when the set of online users changes** (a `frozenset` compare, so reordering never republishes). `services/presence.py` `online_users()`/`online_candidates()` read it with an indexed `last_seen` query (`database.get_online_users`; `idx_users_last_seen`), ordered **alphabetically by username** so an avatar keeps a stable position instead of jumping around as activity ticks. The feed renders the initial list server-side and `static/js/OnlineUsers.js` (`app.onlineUsers`) subscribes to the topic and re-renders the avatar list live. Roster avatars are online by definition, so their dots are a plain green `.presence-dot` with no per-user subscription - list membership *is* the presence. **The avatar dot.** The corner dot is one reusable partial, `templates/_presence_dot.html`, which emits a `` (guarded on `uid`, so an author with no resolvable user renders no dot). It is included by the shared avatar partial `templates/_avatar_link.html` (which covers most avatars) and by a handful of raw-avatar sites wrapped in a positioned `.avatar-badge` span. Because the dot carries `data-presence-uid` but no `data-presence-label`, `PresenceManager` drives its colour with **no extra JavaScript**. The dot sizes itself as a percentage of the avatar (clamped 8-14px), so it stays proportional at every avatar size. Reuse `is_online`, `_presence_dot.html`, the `public.presence.{uid}` topic, and `PresenceManager` for any new online indicator rather than re-implementing presence. {% endraw %}