# Activity JSON schema (v1) This is the contract between the tracker (which writes these files) and the dashboard (which reads them). One file per local calendar day, named `-YYYY-MM-DD.json`, e.g. `a7f3c2d1-2026-09-01.json`. The prefix keeps files from different people or machines from overwriting each other when they are collected in one folder. Everything in the file is derived from four things the tracker observes: which window has keyboard focus, which top-level windows are visible, how long since the last keyboard/mouse input, and which program (if any) currently has the microphone open. Nothing else is collected. Each sample is in exactly one of three states: - **active**: the user gave input recently; time goes to the focused window. - **attending**: no recent input, but a meeting is in progress (Teams/Zoom/Meet window visible, or an app has the microphone open). Time goes to the meeting app and meeting title rather than to idle. - **idle**: no recent input and no meeting. The dashboard labels this "away from desk", since the tracker only knows the keyboard and mouse were still; the field name stays `idleSeconds`. ## Top level | Field | Type | Meaning | |-----------------|--------|---------| | `schemaVersion` | number | Always `1` for this format. The dashboard refuses files with a version it doesn't know. | | `generator` | object | `{ "name": "doka-activity-tracker", "version": "0.1.0" }`: which program wrote the file. | | `date` | string | Local calendar date the file covers, `YYYY-MM-DD`. | | `trackerId` | string | Identifies which tracker installation wrote the file; also the file name prefix. By default 8 random hex characters generated once on first run and kept in `tracker.id`; can be set to any short label in `config.json`. Not derived from the user or machine name. The dashboard keys days by `trackerId` + `date`, so two people's files for the same date can be loaded together. | | `config` | object | The tracker settings that were in effect (see below). | | `summary` | object | Whole-day totals (see below). | | `apps` | array | One entry per program that had a visible window (see below). | | `timeline` | array | Contiguous focus segments in chronological order (see below). | | `meetings` | array | Spans during which a meeting was detected (see below). | All timestamps are **local time** in ISO 8601 without a timezone suffix, e.g. `2026-09-01T08:14:05`. Every duration is in **whole seconds**. ## `config` | Field | Type | Meaning | |--------------------|---------|---------| | `pollIntervalSec` | number | How often the tracker sampled. Every duration in the file is a multiple of this. | | `idleThresholdSec` | number | Seconds without input after which samples count as idle instead of active. | | `captureTitles` | boolean | `false` means window titles were replaced by the app name before writing. | | `meetingDetection` | object | `{ "microphone": true, "rules": [...] }`: whether the microphone check was on and which title rules were used. | ## `summary` | Field | Type | Meaning | |-----------------|--------|---------| | `firstSample` | string | Timestamp of the first sample of the day. | | `lastSample` | string | Timestamp of the most recent sample (the file is rewritten while the tracker runs). | | `activeSeconds` | number | Samples where the user was not idle. | | `attendingSeconds` | number | Samples where the user was idle but a meeting was in progress. | | `idleSeconds` | number | Samples where the user was idle and no meeting was in progress. | | `meetingSeconds` | number | Samples where a meeting was in progress, regardless of input (so `>= attendingSeconds`). | | `samples` | number | Total number of samples taken. `activeSeconds + attendingSeconds + idleSeconds == samples * pollIntervalSec`. | ## `apps[]` | Field | Type | Meaning | |-----------------|--------|---------| | `process` | string | Process image name without `.exe`, e.g. `acad`, `OUTLOOK`, `chrome`. Stable key for colours and merging across days. | | `name` | string | Human-friendly name from the executable's file description (e.g. `AutoCAD`, `Microsoft Outlook`). Falls back to `process`. | | `activeSeconds` | number | The app's window had focus **and** the user was not idle. | | `attendingSeconds` | number | The user was idle while this app was hosting a meeting. Only meeting apps have a non-zero value. | | `openSeconds` | number | At least one visible window belonging to this process existed, focused or not. Always `>= activeSeconds + attendingSeconds`. | | `windows` | array | Per-window breakdown (see below). | ## `apps[].windows[]` | Field | Type | Meaning | |-----------------|--------|---------| | `title` | string | The window title as shown in the title bar. For AutoCAD this includes the drawing name; for Outlook the folder or message subject. | | `activeSeconds` | number | This exact window had focus and the user was not idle. | | `attendingSeconds` | number | The user was idle while this window was the detected meeting. | | `openSeconds` | number | This window was visible. | | `firstSeen` | string | Timestamp of the first sample that saw this window. | | `lastSeen` | string | Timestamp of the most recent sample that saw this window. | Note that titles change as you work (e.g. Outlook shows the selected message), so one real window can appear as several entries. The dashboard groups by `process` first, then lists titles beneath. ## `timeline[]` Focus history, compressed: consecutive samples with the same `process`, `title` and `state` are merged into one segment. Idle time is a segment with `"process": "idle"` and no `title`. | Field | Type | Meaning | |-----------|--------|---------| | `start` | string | Timestamp of the first sample in the segment. | | `end` | string | Timestamp of the last sample in the segment plus `pollIntervalSec`. | | `process` | string | Process name, or `idle`. | | `title` | string | Window title (omitted for idle). | | `state` | string | `"attending"` when the segment is idle-in-a-meeting time attributed to the meeting app. Omitted for ordinary active segments. | Gaps between segments mean the tracker was not running (machine asleep, tracker stopped). ## `meetings[]` Spans during which a meeting was detected, independent of what the user was focused on (you can be in a Teams call while typing in AutoCAD). The dashboard draws these as a lane above the focus timeline. | Field | Type | Meaning | |-----------|--------|---------| | `start` | string | Timestamp of the first sample that detected the meeting. | | `end` | string | Timestamp of the last sample plus `pollIntervalSec`. | | `process` | string | The app hosting the meeting (e.g. `ms-teams`, `Zoom`, `chrome`). | | `title` | string | The meeting window's title, e.g. `Weekly coordination | Microsoft Teams`. | | `source` | string | `"window"` (a visible window matched a meeting rule), `"microphone"` (the app had the mic open), or `"both"`. | How a meeting is detected (both are local checks, both can be turned off): 1. **Window rules**: a visible window whose process and title match one of `config.meetingDetection.rules`. The defaults cover Teams (a window ending in `| Microsoft Teams` that is not one of the main tabs like Chat/Calendar), Zoom (`Zoom Meeting`), and Google Meet in a browser (`Meet - …`). 2. **Microphone**: Windows records which apps currently have the microphone open under `HKCU\Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone` (a `LastUsedTimeStop` of `0` means "in use right now"). This catches calls whose window title doesn't match a rule. ## Category rules file (`categories.json`) Written and read by the dashboard only; the tracker never sees it. Drop it on the dashboard together with day files and it is applied instead of the built-in category defaults. ```json { "type": "doka-activity-categories", "version": 1, "categories": [ { "id": "drafting", "name": "Drafting & modeling" }, { "id": "collab", "name": "Collaboration" }, { "id": "meetings", "name": "Meetings" }, { "id": "other", "name": "Other" } ], "apps": { "acad": "drafting", "Bluebeam": "drafting", "OUTLOOK": "collab" } } ``` | Field | Meaning | |--------------|---------| | `type` | Always `doka-activity-categories`; this is how the dashboard tells it apart from a day file. | | `categories` | Display order and names. `meetings` and `other` are always present (added if missing). Colours follow this order. | | `apps` | Process name to category id. Apps not listed fall back to the built-in name patterns, then to `other`. | `meetings` is never assigned to an app: it is the time a meeting was in progress, computed from `timeline[]` and `meetings[]` (attending segments, plus the part of any active segment that overlaps a meeting span). ## What is deliberately absent No hostname, username, file paths, URLs, keystrokes, clipboard contents, or screenshots. The `trackerId` is random unless you choose one yourself. Window titles are the most sensitive field; set `captureTitles: false` in the tracker config to keep them out of the file. ## Example ```json { "schemaVersion": 1, "generator": { "name": "doka-activity-tracker", "version": "0.1.0" }, "date": "2026-09-01", "trackerId": "a7f3c2d1", "config": { "pollIntervalSec": 5, "idleThresholdSec": 300, "captureTitles": true, "meetingDetection": { "microphone": true, "rules": [ { "process": "^ms-teams$", "title": "\\| Microsoft Teams$", "exclude": "^(Chat|Calendar|Activity|Calls|Apps|Teams and channels) \\|" } ] } }, "summary": { "firstSample": "2026-09-01T07:58:05", "lastSample": "2026-09-01T17:12:40", "activeSeconds": 24400, "attendingSeconds": 3000, "idleSeconds": 5600, "meetingSeconds": 4200, "samples": 6600 }, "apps": [ { "process": "acad", "name": "AutoCAD", "activeSeconds": 7200, "attendingSeconds": 0, "openSeconds": 28800, "windows": [ { "title": "Autodesk AutoCAD 2025 - [Site-Plan-A.dwg]", "activeSeconds": 2400, "attendingSeconds": 0, "openSeconds": 28800, "firstSeen": "2026-09-01T08:02:10", "lastSeen": "2026-09-01T17:12:40" } ] } ], "timeline": [ { "start": "2026-09-01T07:58:05", "end": "2026-09-01T08:02:10", "process": "OUTLOOK", "title": "Inbox - Microsoft Outlook" }, { "start": "2026-09-01T08:02:10", "end": "2026-09-01T08:41:25", "process": "acad", "title": "Autodesk AutoCAD 2025 - [Site-Plan-A.dwg]" }, { "start": "2026-09-01T10:00:05", "end": "2026-09-01T10:31:15", "process": "ms-teams", "title": "Weekly coordination | Microsoft Teams", "state": "attending" }, { "start": "2026-09-01T12:01:00", "end": "2026-09-01T12:44:30", "process": "idle" } ], "meetings": [ { "start": "2026-09-01T10:00:05", "end": "2026-09-01T10:58:20", "process": "ms-teams", "title": "Weekly coordination | Microsoft Teams", "source": "both" } ] } ```