▍ humdrum codex / ticktock v0.0.2
license AGPL-3.0
12.0 KB raw

tt autotrack daemon + activity/calendar timeline — design

Goal (plain language)

A background daemon quietly records what the user is doing — frontmost app, window title, active browser tab, and idle/away state — and appends it to a daily log. The tt timeline (grid) view then shows that captured activity in its ACTIVITY column, side by side with the day's calendar events, in a clean readable form. The user reads the timeline to see where their time actually went and to log it into tock.

The user does not interact with the daemon directly; it runs under launchd. tock stays the source of truth — the daemon writes only to its own staging log, never to tock (suggest-then-confirm).

Background

ticktock-old (Python) already does this: daemon/autotrack.py polls context and writes ~/.local/share/ticktock/activity-YYYY-MM-DD.jsonl; tock-day merged that log with work-calendar events from a compiled Swift cal-events (EventKit) helper. tt has replaced the day/grid views in Go but has neither the daemon nor the activity/calendar reader. This design ports both, natively, into the single tt binary.

The daemon and reader share the existing directories unchanged:

Because the paths are unchanged, cutover is seamless: the Go daemon writes the same stream the Python one did, and the reader sees one continuous history.

Non-goals (this port)

Architecture

Two subsystems, both inside the tt binary.

                          ┌──────────────────────────────┐
  launchd ── tt autotrack │ internal/autotrack            │
                          │  darwin IO ─▶ Classify ─▶ Step │──▶ activity-YYYY-MM-DD.jsonl
                          └──────────────────────────────┘            │
                                                                      ▼
  cal-events (Swift/EventKit) ──▶ calendar JSON ────┐        ┌──────────────────┐
                                                    ├──────▶ │ internal/activity │
                          activity-YYYY-MM-DD.jsonl ─┘        │  LoadDay + merge  │
                                                             └──────────────────┘
                                                                      │
                                                                      ▼
                                                     internal/tui/grid ACTIVITY column

A. Writer — tt autotrack

New package internal/autotrack:

internal/config gains a Tracking block read from the same file:

type Tracking struct { PollSecs, IdleGraceSecs, MinSecs int } // defaults 5, 900, 15

read from config.json's existing tracking object; missing/malformed keys fall back to defaults, matching the Python load_tracking.

cmd/ticktock gains an autotrack subcommand:

B. Reader — ACTIVITY column (activity + calendar)

New package internal/activity:

Calendar selection. The user keeps several calendars for different purposes and must be able to silence the noisy ones. cal-events already returns every event tagged with its calendar name, so filtering is config-driven in the reader — no change to the helper. internal/config gains a calendars block:

"calendars": {
  "show": ["Work", "Focus"]   // allowlist: only these calendars appear in the timeline
}

Semantics: if show is non-empty, only listed calendars pass (case-insensitive exact match on the calendar name); if show is absent or empty, all calendars pass (current behaviour). An optional "hide": [...] blocklist is applied after show for the "everything except a couple" case. To discover the exact names, tt autotrack --list-calendars prints the distinct calendar names cal-events reports for today, so the user can copy them into config. CalendarFilter is built from this config and passed into LoadCalendar.

Grid integration (mirrors the existing LOGGED painting):

Native helpers (Swift) build

winctx (AX window context) and cal-events (EventKit) are compiled Swift binaries. Vendor the two .swift sources into the tt repo (e.g. native/winctx.swift, native/cal-events.swift) and add Makefile targets that build them to bin/winctx and bin/cal-events via swiftc. The build skips gracefully when swiftc is unavailable (prints a note, continues) — the daemon degrades to System Events titles and the reader simply shows no calendar events, so a machine without Xcode tools still builds and runs tt.

launchd cutover

Error handling / degradation

Testing

Cutover & rollback (summary)

  1. Build tt + bin/{winctx,cal-events}.
  2. Verify tests + tt autotrack --once + grid rendering on today's real data.
  3. Repoint launchd to tt autotrack; confirm it writes today's log.
  4. Run a normal day; confirm parity.
  5. Archive ticktock-old.

Rollback at any point: repoint the plist back to autotrack.py.