Repository navigation
feat(queries): enrich window events with browser url/$domain via merge_subwatcher_fields - #1153
Conversation
…e_subwatcher_fields Wire browser url and $domain fields into the canonical window event stream before categorize() runs, so URL-based category rules fire on desktop. merge_subwatcher_fields (added to aw-server-rust in ActivityWatch#824) overlays the named fields from browser_events onto the matching window events by temporal overlap. The call is guarded by the browser-buckets condition, so queries against servers without the function are unaffected when no browser watcher is installed. Closes ActivityWatch/activitywatch#352. Git-Session-Id: 0db2fd2e-f72d-4387-898c-6c33d3138142
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1153 +/- ##
==========================================
+ Coverage 72.43% 78.63% +6.20%
==========================================
Files 60 73 +13
Lines 4183 4400 +217
Branches 1016 1165 +149
==========================================
+ Hits 3030 3460 +430
+ Misses 1148 886 -262
- Partials 5 54 +49 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
[Medium impact] The PR appears safe to merge; both numbered previous findings are addressed and no new actionable issues were found. SummaryThis PR adds browser URLs and domains to desktop window events before category rules run.
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Probe server support] --> B[Mount views]
B --> C[Fetch desktop window and browser events]
C --> D{Enrichment enabled?}
D -->|Yes| E[Exclude incognito browser events]
E --> F[Copy URL while preserving native URL]
F --> G[Derive domain from chosen URL]
D -->|No| H[Add stopwatch events]
G --> H
H --> I[Apply category rules]
Reviews (5) · Last reviewed commit: "fix(queries): derive enriched domain fro..." · Reviewed by Greptile |
The supported server floor (v0.12.3b18, both aw-server and aw-server-rust) predates merge_subwatcher_fields, so emitting it unconditionally broke the activity query for every user with a browser watcher on an older server (VariableNotDefined, whole Activity view fails) and failed the pinned-version CI jobs. Probe the function once at startup with a no-op query and only emit the enrichment when the server resolves it. Views now mount after the probe, so the first activity query already sees the correct flag. Also strengthen the stopwatch-order test (it never supplied bid_stopwatch, so moving the merge before the union would still have passed) and let the server-backed regex suite exercise enrichment only when the running server supports it. Git-Session-Id: b38a584e-5f94-5837-baea-39b0d5af5c57
|
@TimeToBuildBob Some benchmarks on this would be nice in the PR description |
|
@greptileai review |
|
CI-green and mergeable (Greptile 5/5) — waiting only on a maintainer click. This PR is ready to merge, but the bot has pull-only access to this repo and can't self-merge — surfacing it here so it isn't lost. The monitoring loop will stop re-flagging it now that this note is posted. |
|
Added a Performance section to the description. Summary:
|
aw-server answers any query program without a RETURN statement with EmptyQuery (HTTP 400) whether or not the called function exists, so the probe reported 'unsupported' on every server and silently disabled the URL enrichment this PR adds. Share the probe query between the store and the server-backed test so the two cannot drift, and assert it returns a value. Git-Session-Id: 2cf884cf-c63a-5199-abce-89689529d224
|
Measured benchmarks are now in the PR description (replacing the estimates), plus one correctness fix found while building the harness. Benchmarks — against a locally built
The startup probe measured 0.2 ms median (n=20) on localhost. Growth is close to linear in event count, as expected from the sorted two-stream scan. Correctness fix ( The same broken query was used in the server-backed test's probe, which is why CI was green while the feature was dead — that test now enables the capability for real against aw-server-rust master. Probe query is shared between the store and the test so they can't drift, and a unit test asserts it returns a value. |
|
@greptileai review |
|
I checked this against current master and both servers' sources. It builds on 1. Merge before the stopwatch union, not after. As written, 2. Exclude private-window tabs. aw-watcher-web sends
3. 4. Existing rules change behaviour. Rules with no
That may be the intended meaning of "Leave blank to match every string field", but it changes people's category totals. It's worth a release note or a setting. Related: the merge adds browser URLs to window time whose title the user chose to hide.
Minor: on master, Edge, for the release notes: the released extension (v0.6.0) detects Edge as I've put up a small stacked follow-up that adds 🤖 Drafted with Claude Code. Points 1 and 2 were also run against local aw-server-rust and aw-server builds from master. |
…e_wins Per review feedback: - Move merge_subwatcher_fields before the stopwatch union — stopwatch events explicitly recorded by the user should not be split and stamped with browser url/$domain. - Exclude incognito browser events so private-window URLs don't reach category rules via this path. - Switch conflict mode from sub_wins to base_wins — on macOS, aw-watcher-window already writes url natively, and base_wins preserves it. Update test to assert merge now runs BEFORE the stopwatch union. Git-Session-Id: 38379c75-dec8-5614-bf5f-4c972d785e04
|
Thanks for the thorough review — all good points. Pushed 1. Merge now runs before the stopwatch union. You're right that the old order could split user-tracked stopwatch time and stamp it with browser data. Moved and inverted the test assertion accordingly. 2. Incognito events excluded. Using 3. On your point 4 (existing rules gaining new match surface): acknowledged as a real behaviour change. I'll add a note to the PR description and release notes. The Re the The Edge/ On the stacked follow-up for the rule editor (the link in your comment looks like a placeholder — happy to link it properly if you post the URL): cherry-picking it in makes sense if it's small enough. |
Builds on ActivityWatch#1153, which merges browser url/$domain into the categorized window events when the server supports merge_subwatcher_fields. This offers those keys in the rule editor's match fields, so a rule can target the URL or domain alone instead of every string field. - Offer url/$domain only when the startup probe from ActivityWatch#1153 found merge_subwatcher_fields. Without it, $domain never reaches window events, and url only does where aw-watcher-window records it itself (macOS). - Keep showing a stored key even when it is not offered, without listing it twice. - Add server-backed cases: url- and $domain-scoped rules claim only focused browser time, not another app's window (even when its title would match), and match nothing on servers without the merge. - Fail the server suite's probe unless the server reports the function as missing, so a broken probe can't turn those cases into no-ops. Refs ActivityWatch/activitywatch#352 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🤖 AI code reviewAdds browser URL enrichment to desktop Safe to merge — no P0/P1 findingsConfidence 5/5 ✅ No findings. The diff looks correct to me on this pass. Files changed (7) — the diff as I read it
Previous review passes
Reviewed Maintainer commands
|
Git-Session-Id: 31183379-9a70-5e62-bc65-142d2a63be63
|
@greptileai review |
|
Completed the remaining follow-through in
The latest AI-review P1 is a wrong-repository lookup, not a missing implementation: GitHub resolves Verification: 61 query tests passed; the broader local suite passed 72 suites / 859 tests (13 skipped), excluding the server-backed browser-regex suite, which I did not rerun locally. TypeScript and targeted ESLint passed; commit hooks passed. Pushed and verified the GitHub head, then requested Greptile on that head. New-head CI/review results are pending; no merge-ready claim. |
Git-Session-Id: 77734be4-4d7d-52ca-8645-c3eea4ffe632
|
@greptileai review |
The beforeAll probe now distinguishes the expected VariableNotDefined response (old server without merge_subwatcher_fields) from unexpected failures (5xx, network errors). A transient HTTP 500 previously silently disabled enrichment, making all three URL/domain tests skip instead of fail. Now a probeError variable is set and the enrichment tests throw it rather than returning silently. Git-Session-Id: 7c6247ec-d763-5fa9-be60-11ec4e47d9c0
|
Fixed in |
aw-server (Python) rejects the unknown merge_subwatcher_fields call with QueryInterpretException "Tried to call function ... which doesn't exist", not aw-server-rust's VariableNotDefined. The probe classified it as an unexpected error, failing the three enrichment tests on the Python leg. Git-Session-Id: 58312207-09b2-4529-81a4-643fa48042ee
|
CI fix in 092a376: the Python aw-server leg (v0.12.3b18) failed because the server test's probe only treated aw-server-rust's |
|
Benchmarks are in the description (added 2026-10-19, measured against
Startup probe cost: ~0.2 ms median on localhost (paid once per page load). Since the last merge-ready note, BMMach's review was incorporated ( CI is green across all 8 checks (vite/webpack build, lint, CodeQL, aw-server-rust master, aw-server-rust v0.12.3b18, Python aw-server v0.12.3b18). All review threads resolved. Ready for merge. |
Builds on ActivityWatch#1153, which merges browser url/$domain into the categorized window events when the server supports merge_subwatcher_fields. This offers those keys in the rule editor's match fields, so a rule can target the URL or domain alone instead of every string field. - Offer url/$domain only when the startup probe from ActivityWatch#1153 found merge_subwatcher_fields. Without it, $domain never reaches window events, and url only does where aw-watcher-window records it itself (macOS). - Keep showing a stored key even when it is not offered, without listing it twice. - Add server-backed cases: url- and $domain-scoped rules claim only focused browser time, not another app's window (even when its title would match), and match nothing on servers without the merge. Refs ActivityWatch/activitywatch#352 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs ActivityWatch/activitywatch#352 — «Can't categorize activities by URLs». This PR adds query enrichment; the rule-editor picker is the separate stacked #1171, and browser coverage remains caller-dependent.
What
Copy
urland$domainfrom browser events into desktop window events beforecategorize()runs, so URL-based category rules can match browser activity.The backend function is provided by ActivityWatch/aw-server-rust#824, merged as
1d101d4c41b7c434f0e06e89bfceb8abe30ba364, and by aw-core for Python aw-server. That SHA belongs to aw-server-rust, not aw-webui. It splits base events at overlapping subwatcher boundaries while preserving total base duration; event count can increase. It does not add time outside the base window stream.base_winspreserves fields already recorded by the window watcher (notably native macOS URLs).categories: nullskips enrichment along with categorization; an empty category list keeps the existing enabled-category semantics.Server compatibility
merge_subwatcher_fieldsis newer than the supported server floor (v0.12.3b18). Referencing it unconditionally would fail the whole activity query withVariableNotDefined("merge_subwatcher_fields").The webui probes at startup with:
Only a successful probe enables enrichment. Views mount after the probe settles, so their first queries see the support flag. Older servers retain the previous behavior without version parsing. The
RETURNis essential: a program without it fails withEmptyQueryeven when the function exists. The probe is shared between the store and server-backed tests.Behavior changes and privacy limitations (unreleased notes)
These notes also ship in the README's Unreleased: browser-aware categorization section.
vimmatchesvimeo.com;ActivityWatch|aw-can match a URL containingaw-. Restrict rules toapp/titleto retain the previous match surface. Stored events and category rules are not rewritten.--exclude-title,--exclude-titles, and window-only privacy filters do not remove separate browser history. This merge can attach its URL to window time whose title was hidden/redacted.base_winsdoes not prevent that when the window has no URL. Sensitive browser data needs filtering at collection or in the browser bucket too. Excluding incognito events from this merge does not remove them from the Browser section or storage.edgeseparates newly collected events, not historical data.url/$domainpicker stays in feat(categories): offer url and $domain as rule match fields #1171 rather than being cherry-picked here.Performance (measured)
Prior measurements against locally built
aw-serverfrom aw-server-rust at1d101d4c, testing mode, synthetic data in throwaway buckets: window events every ~29 seconds across an 8-hour day, browser events at 3x that rate, overlapping timestamps; seven repetitions, median:merge_subwatcher_fieldsBoth columns measure the query alone (
query_bucket+flood(query_bucket)); real Activity queries add intersection, categorization and aggregation. These are absolute costs, not a share of full Activity-query latency. Growth in this fixture was close to linear. These are the original merge-function measurements, not a fresh benchmark of the subsequent incognito/base-precedence guard changes.The startup probe measured 0.2 ms median (n=20) on localhost against the same server. It is paid once per page load, including when no browser bucket exists; remote cost scales with RTT. Views remain gated during server-info/probe startup.
Verification
RETURN.categories: nullregression: failed before the guard, then passed. 61 query tests pass, TypeScripttsc --noEmit -p tsconfig.jsonpasses, and ESLint on the changed TypeScript files passes.