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, orlast seen ...with a relative time, orofflineif 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 <span class="presence-dot" data-presence-uid=... data-presence-last-seen=...> (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.