feat(activity): choose all devices or a subset of devices in the Activity view - #1004
Conversation
|
@greptileai review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1004 +/- ##
==========================================
+ Coverage 58.78% 61.31% +2.52%
==========================================
Files 51 51
Lines 3254 3428 +174
Branches 799 841 +42
==========================================
+ Hits 1913 2102 +189
+ Misses 1265 1247 -18
- Partials 76 79 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 95c950409a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
95c9504 to
f9a72f6
Compare
|
@greptileai review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f9a72f62c2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
f9a72f6 to
2647a2e
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2647a2e9d1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
🤖 AI code reviewThis PR replaces the developer-setting-gated multidevice query with a first-class device selector in the Activity view. It adds a route-based host selection scheme (single host, comma-separated list, or @ALL), a dropdown UI in Activity.vue and Header.vue, and reworks the activity store to query multiple devices with per-host bucket overrides, combined active history, category-by-period, and ScreenTime app-name resolution. It also removes the useMultidevice setting and adds several unit tests for the new host-selection and query logic. Safe to merge — no P0/P1 findingsConfidence 5/5 ✅ No thread-worthy findings. Advisory notes follow; they are retained without opening review threads. 5 advisory findings (summary-only, not scored)These P2 guard, heuristic, trade-off, or documentation claims are retained for judgment without opening review threads.
In ensure_loaded_multidevice, the availability flags are set based on How this was verified: Checked the logic in ensure_loaded_multidevice lines 464-475. The multidevice query includes both desktop and android hosts, and the returned app_events include mobile apps. But the availability flag android.available is set to false whenever hasDesktop is true, so the view will not render Android-specific visualizations even though the data is present. The test at line 124 expects this behavior, but it is a design choice that hides mobile data in combined views.
In parseHostParam, the logic for a comma-separated list checks How this was verified: Checked the order of checks in parseHostParam: lines 103-125. The list check at line 114 comes before the knownHosts.includes(param) check at line 117. The test at line 49-59 explicitly tests the case where 'a,b' is a known host and 'a' and 'b' are also known, expecting the list interpretation. This means existing raw URLs to a comma-containing hostname are broken if the split pieces are also known hosts. The PR description says the opposite.
In query_category_time_by_period, when multideviceParams is used, the query is built with How this was verified: Traced the flow: ensure_loaded_multidevice sets query_hosts before querying; category query runs after. Abort on new load should cancel pending requests.
In Header.vue, the 'All devices' dropdown item is shown only when How this was verified: Checked the activityViews construction and the route definitions (not shown but inferred).
In multideviceActivityQuery, the mobile hosts' app-usage events are unioned into not_afk via How this was verified: Compared with activityQueryAndroid which also sums all events. Files changed (19) — the diff as I read it
Previous review passes
Reviewed Maintainer commands
|
Don't pre-merge Android watcher events by app in the multidevice query. merge_events_by_keys collapses each app into one event at its first timestamp with the summed duration, in arbitrary order, so the per-device timeline passed to union_no_overlap was fabricated: events overlapped each other and landed in the wrong hours. The single-device Android view keeps the pre-merge, since it only computes per-app totals.
2647a2e to
1936bc9
Compare
|
@greptileai review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1936bc98c2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
1936bc9 to
a70789d
Compare
|
@greptileai review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a70789d167
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
a70789d to
c646ba3
Compare
|
@greptileai review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c646ba33fa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…vity view Multidevice was hidden behind the "Use multidevice query" developer setting, which silently switched every Activity URL to a combined query over all hosts. Make it a first-class choice instead: the Activity view gets a device selector (All devices, or any subset via checkboxes, plus "only" to jump to a single device), and the Activity nav menu gets an "All devices" entry. The selection lives in the existing :host route param: /activity/<host>/... single device (unchanged) /activity/host1,host2/... a subset of devices /activity/@all/... all devices with activity data '@' and ',' don't occur in DNS hostnames. A param that exactly matches a known hostname is always that single host, and list items are escaped, so free-form bucket hostnames (e.g. ScreenTime imports, which contain commas) work both alone and in a list. In multidevice mode the store now also: - includes synced (-synced-from-) and Android/ScreenTime hosts; - computes the period-usage bars from all selected devices (new multideviceActivityQuery; simultaneous use counts once), with the same bounded request span as the single-device active history (#1003); - computes category-by-period (timeline barchart) from the combined events, which previously always used the route's single host; - includes editor buckets of all selected devices; - clears the cached active history when the selection changes. Browser and stopwatch data remain single-device only, as before; the view says so when several devices are selected. The useMultidevice setting and its developer toggle are removed: the route now expresses the same thing per view, and keeping a global switch would make single-host URLs ambiguous. Stale stored values are skipped via REMOVED_KEYS, like showYearly in #1003.
… period The period-usage history cache was checked with _.includes(history, period), which searches the cached event lists rather than the period keys, so it never hit and every navigation re-queried the whole surrounding range (up to 16 years in Year view). Check the keys, and keep re-querying the period that contains now, since it's still growing. Applies to the single-device, Android and multidevice history queries.
c646ba3 to
c90589e
Compare
|
@greptileai review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c90589e6dc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
useMultidevice is gone (#1004): multi-device views are now selected by the route (@ALL or a host list) and loaded by ensure_loaded_multidevice. - ensure_loaded_multidevice follows what the single-device path does for custom ranges and All time: no period-usage history when it's skipped, the monthly barchart derived from the query chunks for long ranges (and no years of active events kept in state), and progress cleared. - get_earliest_date covers the devices the query will include. - The single-device monthly-barchart shortcut no longer checks useMultidevice.
useMultidevice is gone (#1004): multi-device views are now selected by the route (@ALL or a host list) and loaded by ensure_loaded_multidevice. - ensure_loaded_multidevice follows what the single-device path does for custom ranges and All time: no period-usage history when it's skipped, the monthly barchart derived from the query chunks for long ranges (and no years of active events kept in state), and progress cleared. - get_earliest_date covers the devices the query will include. - The single-device monthly-barchart shortcut no longer checks useMultidevice. - Device-selector links keep the route's date part, so switching devices keeps a custom range (and doesn't add a date to All time).
* feat(activity): add custom date range period Adds a 'custom range' period to the Activity view, encoded in the URL as /activity/:host/range/YYYY-MM-DD..YYYY-MM-DD (both ends inclusive), so ranges are shareable and existing URLs keep working. - Start/end native date inputs replace the single date input in range mode - Prev/next step by the range length; next is disabled once it would start after today (this also fixes the next button never being disabled, since `today` was never set) - Entering range mode from another period keeps the shown period, clipped to today - Ranges longer than 92 days are bucketed by calendar month in the timeline barchart and the category-by-period query (shared via timeperiodsForBarchart); multi-day barchart labels now show dates Part of ActivityWatch/activitywatch#1465 * perf(activity): chunk long ranges by week and derive monthly barchart For ranges long enough to use monthly barchart buckets, split the desktop query into chunks of at most 7 days that never cross a calendar month, and build the monthly category data from those chunk results instead of issuing a month-sized category query per month (10-38s each on a 1.7 GB database, occasionally past the 30s request timeout). * perf(activity): keep per-day requests for long ranges Week-sized chunks were no faster on a 1.7 GB aw-server database and single requests reached 20s, too close to the 30s timeout. Days never cross a month boundary either, so the monthly barchart is still derived from them. * fix(activity): address review on custom ranges - Skip the period-usage history in range mode and hide its bars: 31 neighbouring ranges can span decades of AFK data - Cap the range end at today - Hide prev/next when the URL range is invalid (they produced invalid links) - Use the app locale for barchart date/month labels - Remove a test file that belongs to the All time PR * fix(activity): clip next range at today * feat(activity): add All time period (#1006) * feat(activity): add All time period - New 'all' period (/activity/:host/all/view/...) from the host's first day with data to today, marked with 🐌 plus a slowness hint and a progress bar - Earliest event: metadata.start on aw-server-rust, otherwise a ~15-request bisection with GET /events?end=&limit=1 on aw-server (Python); cached per host - Skips the period-usage history (no neighbouring periods) - Editor and Android/ScreenTime queries are split into bounded chunks and merged client-side instead of one request for the whole span - Long ranges don't keep years of AFK events in reactive state Part of ActivityWatch/activitywatch#1465 * fix(activity): address review on All time - Earliest date covers every bucket type the view queries (editor, browser, stopwatch too) and all hosts when multidevice is on; cache keyed by hosts and day-start offset - Bisection returns a conservative bound (never after the first event) - Earliest-event lookup failure falls back to bucket creation dates instead of blocking the page - Chunked editor/Android queries over-fetch (1000 per chunk) so items outside each chunk's top 100 can still reach the overall top 100 - A failed Android chunk shows no data instead of partial totals - Progress bar counts editor and Android chunks * refactor(activity): use the #1004 host selection for ranges and All time useMultidevice is gone (#1004): multi-device views are now selected by the route (@ALL or a host list) and loaded by ensure_loaded_multidevice. - ensure_loaded_multidevice follows what the single-device path does for custom ranges and All time: no period-usage history when it's skipped, the monthly barchart derived from the query chunks for long ranges (and no years of active events kept in state), and progress cleared. - get_earliest_date covers the devices the query will include. - The single-device monthly-barchart shortcut no longer checks useMultidevice. - Device-selector links keep the route's date part, so switching devices keeps a custom range (and doesn't add a date to All time).
Motivation
Multidevice was only reachable through the "Use multidevice query" developer setting, which silently turned every
/activity/<host>URL into a combined query over all hosts. This makes it a first-class choice in the Activity view: pick All devices, any subset of devices, or a single device, and the choice is part of the URL.What changed
Device selector. The "Host:" line in the Activity header becomes a dropdown (shown when there is more than one device):
The nav "Activity" menu gets an All devices entry (only when there are 2+ devices), and the landing-page setting offers "Activity (All devices)".
Route scheme. The selection lives in the existing
:hostparam, so no new routes:/activity/<host>/<period>/<date>/activity/host1,host2/<period>/<date>/activity/@all/<period>/<date>@and,don't occur in DNS hostnames. Bucket hostnames are free-form though (ScreenTime imports produce hostnames likeios-('2E78...', None)), so:[A-Za-z0-9._-]) are unaffected, so URLs stay readable.A selection that resolves to one device (e.g.
@allon a single-device install) uses the regular single-device view with all its features.Store/query path (
src/stores/activity.ts), when several devices are selected:-synced-from-<host>bucket ids, via the existingbuildMultideviceHostParams) and Android/ScreenTime hosts;multideviceActivityQuery: desktop not-afk periods and mobile app usage,period_unioned so simultaneous use counts once), requested in the same bounded spans as the single-device history from feat(activity): always show Year period, remove showYearly setting #1003. Previously they were afk-only, so phones contributed nothing;work-laptop,worklaptop) can't overwrite each other;Browser and stopwatch data stay single-device only, as before; the header says so when several devices are selected.
useMultidevicesetting: removed. The route now expresses the same thing per view. Keeping a global switch would make single-host URLs mean different things depending on a hidden setting, and it was a dev-only "early experiment" toggle. Users who had it enabled can use the new All devices entry. Stale stored values are skipped viaREMOVED_KEYS, likeshowYearlyin #1003.Bug fixed along the way
Android events were pre-merged before the cross-device union. In
canonicalEvents, Android watcher events go throughmerge_events_by_keys(events, ["app"]). That's fine for the single-device Android view, which only computes per-app totals, but in the multidevice query the merged events are then fed tounion_no_overlapas a timeline.merge_events_by_keyscollapses each app into one event at its first timestamp with the summed duration, and (in aw-server-rust) returns them in hash-map order, whileunion_no_overlapexpects sorted, non-overlapping input. Measured on a real synced phone bucket for one day:The multidevice query now keeps the raw (flooded) events (
AndroidQueryParams.keep_event_timestamps); the single-device Android query is unchanged.Active-history cache (separate commit, pre-existing): the cache was checked with
_.includes(history, period), which searches the cached values rather than the period keys, so it never hit and every date step re-queried the whole surrounding range (up to 16 years in Year view). It now checks keys and always re-queries the period containing now. Stepping back one day went from 17 periods per request to 2.Testing
test/unit/hostSelection.test.node.ts: parse/format round-trips (including comma and%hostnames), resolution order, toggling, eligible hosts.test/unit/store/activityMultidevice.test.node.ts: selection resolution in the store, synced and Android bucket ids.test/unit/route.test.js: single,@all, list, and escaped-list URLs resolve to the activity view with the expectedhostparam.test/multidevice.test.node.ts: no Android pre-merge in the multidevice query (still present in the single-device one),multideviceActivityQuery.npm test(418 passing),npm run lint,tsc --noEmit,npm run build.Related: ActivityWatch/activitywatch#302 (how hostnames/devices work), #987, #906.
Rebased on #1001 (which now carries the query-comment fix this PR originally duplicated) and #1003.