User activity
Ascendly tracks when employees sign in, when they were last active on web vs desktop, and how many minutes they had the app visible on screen. HR and managers use this on the Employees page; employees see their own summary on Profile (web) or the Home banner (desktop).
Web and desktop are tracked separately — opening Ascendly in a browser and in the desktop app updates different timestamps and minute counters.
What is recorded
| Signal | Storage | Meaning |
|---|---|---|
| Last login | tbl_employees.last_login_at | Most recent successful Google sign-in (web or desktop) |
| Last active (web) | tbl_employees.last_seen_at_web | Most recent time the web client reported a visible foreground tab |
| Last active (desktop) | tbl_employees.last_seen_at_desktop | Most recent time the desktop window was visible on screen |
| Last active (latest) | Derived last_seen_at | Latest of web, desktop, and legacy combined field (used in exports) |
| Active minutes (web) | tbl_user_activity_daily.active_minutes_web | Minutes the web tab was visible today / rolling 7 days |
| Active minutes (desktop) | tbl_user_activity_daily.active_minutes_desktop | Minutes the desktop window was visible today / rolling 7 days |
Active minutes use the employee's site timezone (Cebu/Baybay → Asia/Manila, US → America/New_York) for calendar days and the rolling 7-day window (today plus the prior six site-local days).
How monitoring works
Both clients send a lightweight activity heartbeat to the API at most once per 60 seconds while they qualify as “on screen”:
POST /v1/auth/activity-heartbeat
{ "client": "web" | "desktop" }On each successful heartbeat the server:
- Increments active minutes — at most one minute per clock minute (site timezone) for that client.
- Updates last active for that client — throttled to at most once every 5 minutes per client (
last_seen_at_weborlast_seen_at_desktop).
Heartbeats are the only source of last-active updates. Background API calls (for example notification polling on a hidden tab) do not bump last active.
Visibility rules
| Client | Counts as active when | Does not count |
|---|---|---|
| Web | Ascendly is the foreground tab in a visible browser window (document.visibilityState === 'visible') | Background tab; browser minimized; tab discarded by the browser |
| Desktop | Main window is visible on screen (not minimized) | Window minimized; app hidden in system tray |
No mouse or keyboard interaction is required. If Ascendly is visible on a second monitor as the active tab (web) or an on-screen window (desktop), heartbeats continue even when another app has OS focus on the primary monitor.
When visibility returns (for example you switch back to the Ascendly tab), the client sends a heartbeat immediately.
Sign-in
On Google OAuth sign-in, Last login is set and the matching client last-active field is updated (client: "web" or client: "desktop" in the token exchange).
Where to see it
Employees (managers / HR — access level 2+)
On Employees (/employees):
| Column | Source |
|---|---|
| Last active (web) | last_seen_at_web |
| Last active (desktop) | last_seen_at_desktop |
Open an employee row for detail: Account activity shows last login, both last-active timestamps, and combined active minutes (today + 7 days).
Export: use the activity export action to download a CSV with last login, last active (web), last active (desktop), last active (latest), and active minute totals.
Profile (self — web)
Account activity on Profile shows:
- Last login
- Last active (web) and Last active (desktop)
- Web active today and Web active (last 7 days)
Desktop minutes appear on desktop Home, not on the shared Profile web-minute fields.
Desktop Home (self)
The welcome banner on BSC Home shows desktop active minutes (today and 7-day rolling) from GET /v1/auth/activity-summary.
Impersonation
When a level-5 admin impersonates another user:
- Heartbeats are ignored — no active minutes and no last-active updates for the impersonated employee.
- The impersonated user's presence metrics are not affected.
Troubleshooting (HR)
| Symptom | Likely cause |
|---|---|
| Last active (web) stale but employee says Ascendly is open | Tab is in the background (another tab selected), browser is minimized, or the tab was discarded (browser memory saver) |
| Last active (desktop) stale | App is in the tray or window is minimized |
| Timestamp lags by ~5 minutes | By design — last active is throttled to limit database writes |
| Web minutes zero but desktop minutes growing | Employee is using the desktop app, not the web tab (check Last active (desktop)) |
| Both last-active empty after deploy | Employee has not had a visible session since the feature shipped; values populate on next sign-in / visible heartbeat |
API (developers)
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/auth/activity-heartbeat | Body { "client": "web" | "desktop" } — record minute + last active |
| GET | /v1/auth/activity-summary | Self: today + 7-day minutes (split by client) |
| GET | /v1/employees/activity-export | Manager CSV export (same scope as employee list) |
Clients send X-Ascendly-Client: web or desktop on API requests for attribution. See API reference.
Design notes (engineering repo): design-log/2026-07-07-user-activity-split-last-seen.md.
Related docs
- Platform & admin — employee list access, impersonation, system tools
- Desktop app — tray behavior and desktop-specific UX
- Notifications — separate from presence; inbox polling does not update last active
