Skip to content

feat(categories): support force_active preset flag to override stored set selection - #973

Closed
TimeToBuildBob wants to merge 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:feat/force-active-preset
Closed

feat(categories): support force_active preset flag to override stored set selection#973
TimeToBuildBob wants to merge 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:feat/force-active-preset

Conversation

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Problem

When a build ships a preset category set via AW_PRESET_CATEGORY_SETS, the preset is only activated on a fresh install — users who had ActivityWatch before the build was deployed never see the preset activated. loadCategories() takes the storedSets && storedSets.length > 0 branch, which preserves whatever active_set_ids were stored (['default']), and skips the preset-activation path entirely.

This matters for research builds and managed deployments where a specific taxonomy must be active regardless of prior install state.

Solution

Add an optional force_active?: boolean field to CategorySet. When a preset has force_active: true, loadCategories() activates it even when the user has stored sets.

[{
  "id": "research-study",
  "force_active": true,
  "categories": [...]
}]

Invariants preserved

  • Only presets delivered via getPresetCategorySets() (build-time env var) can use force_active. A stored set cannot force-activate itself.
  • Single-set activation constraint is kept: only the force_active preset is activated (same as the first-run path), so syncToPrimarySet() can write user edits back correctly.
  • If no force_active preset exists, behaviour is identical to before.
  • The bug fix in getPresetCategorySets()'s deep-copy — it previously stripped force_active from the returned objects because the copy only included { id, categories }.

Changes

  • src/util/classes.ts: Add force_active?: boolean to CategorySet interface; check for force-active preset before restoring stored active_set_ids
  • src/util/presetCategories.ts: Parse and propagate force_active: true in parseSet(); preserve the flag in the getPresetCategorySets() copy
  • test/unit/presetCategories.test.node.ts: 6 new tests covering parser propagation, fresh-install activation, existing-install override, and non-force-active unchanged behaviour

… set selection

Builds that ship AW_PRESET_CATEGORY_SETS can now mark a preset as force_active:
true to ensure the preset is activated even when the user has existing stored
category sets (e.g. the built-in 'default' set from a prior AW install).

Without this, a managed or research build that injects a preset taxonomy via
AW_PRESET_CATEGORY_SETS would only activate the preset on a fresh install
(the !hasStoredCategories branch in loadCategories). Any participant who had
ActivityWatch before the build was deployed would see the research-study set
appear in the set-picker but remain inactive — their stored 'default' selection
taking precedence silently.

Changes:
- Add force_active?: boolean to CategorySet interface (classes.ts)
- In loadCategories(), check for a force_active preset before deciding
  which sets to activate in the stored-sets branch (classes.ts)
- parseSet() in presetCategories.ts now propagates force_active: true
- The getPresetCategorySets() deep-copy now includes force_active so the
  flag survives the cache copy step
- Unit tests cover: parser propagation, fresh-install activation,
  existing-install override, and non-force_active unchanged behaviour

Git-Session-Id: 1e9e
@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.43%. Comparing base (3e7fb7c) to head (076a95a).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #973      +/-   ##
==========================================
+ Coverage   52.25%   52.43%   +0.17%     
==========================================
  Files          48       48              
  Lines        2943     2954      +11     
  Branches      692      698       +6     
==========================================
+ Hits         1538     1549      +11     
  Misses       1385     1385              
  Partials       20       20              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@greptile-apps

greptile-apps Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; both previous findings are addressed and no new actionable failures remain.

Summary

  • Preserves and parses the optional force_active flag.
  • Selects a forced preset on fresh and existing installations.
  • Warns when multiple presets request forced activation.
  • Adds regression coverage for forced selection and unchanged non-forced behavior.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Load stored sets and build presets] --> B{Any force_active presets?}
  B -->|Multiple| C[Warn and select first]
  B -->|One| D[Select forced preset]
  B -->|None| E{Stored sets exist?}
  C --> F{Stored sets exist?}
  D --> F
  F -->|Yes| G[Use stored sets and forced preset ID]
  F -->|No, fresh install| H[Use presets and forced preset ID]
  E -->|Yes| I[Restore stored active IDs]
  E -->|No, presets available| J[Activate first preset]
  E -->|No presets| K[Migrate legacy categories]
  G --> L[Append non-colliding presets]
  H --> L
  I --> L
  J --> L
  K --> L
  L --> M[Validate active IDs and return]
Loading

Comment thread src/util/classes.ts Outdated
// not just fresh ones. We only honour force_active on presets delivered
// via AW_PRESET_CATEGORY_SETS (getPresetCategorySets()), never on stored sets
// — user edits stored under the same id still win over the preset definition.
const forceActivePreset = presets.find(p => p.force_active);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Forced preset skipped initially

When a fresh install receives multiple presets and the force_active preset is not first, the forced-preset lookup is only honored in the stored-sets branch. The fresh-install branch still activates presets[0], so the managed taxonomy remains inactive despite being marked force_active.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 076a95a: the fresh-install branch now checks forceActivePreset first and only falls back to presets[0] when none is present. Added regression test force_active preset wins on fresh install even when it is not presets[0].

}
return { id: raw.id, categories };
const set: CategorySet = { id: raw.id, categories };
if (raw.force_active === true) set.force_active = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Multiple forced presets ambiguous

If a configuration marks multiple presets as force_active, the parser accepts all of them while activation silently chooses the first match. This makes the selected taxonomy depend on array order and can unexpectedly override the user's stored selection without any validation or warning; the configuration should be rejected or clearly diagnosed.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 076a95a: loadCategories() now collects all force_active presets with .filter(), emits a console.warn listing their ids when there are more than one, then takes the first. Ambiguous configs are diagnosable without silent override.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

CI-green and mergeable — 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.

…ultiple force_active presets

P1 (Greptile): when a fresh install receives multiple presets and the
force_active preset is not first in the array, loadCategories() was
activating presets[0] unconditionally — force_active was only honoured
in the stored-sets branch.  Now both branches prefer forceActivePreset
when present.

P2 (Greptile): multiple force_active presets were silently accepted and
the first match was used without any diagnostic.  Now emits a console.warn
listing the conflicting ids so misconfigured managed builds surface the
error immediately.

Tests: 2 new — force_active wins on fresh install even when not presets[0];
warn + first-wins on multiple force_active presets.  All 46 pass.
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@ErikBjare

Copy link
Copy Markdown
Member

@TimeToBuildBob I merged #974, what to do here?

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

#974 and #973 solve different sub-problems:

After #974, users with no real edits get the preset automatically. Users with real edits still won't see it — force_active is the opt-in override for that case.

Recommendation: if the Research Edition only needs to activate the taxonomy for users whose stored data was install-defaults (fixed by #974), close this PR as superseded. If you foresee needing the explicit override for managed/controlled deployments, I can rebase this onto master (the conflict is small — both PRs touch loadCategories and parseSet).

Which way do you want to go?

@ErikBjare

ErikBjare commented Sep 9, 2026

Copy link
Copy Markdown
Member

@TimeToBuildBob i closed this, idk if it was right

@ErikBjare ErikBjare closed this Sep 9, 2026
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Closing was right for now.

#974 already handles the practical case: it distinguishes "auto-saved install defaults" from genuine user edits, so presets activate correctly for research participants who happened to save settings before the build deployed.

#973's force_active flag goes further — it would override even genuine user customizations (someone who deliberately changed their taxonomy). That's a stronger guarantee that's worth evaluating separately after #974 is deployed. If the research edition still needs it (e.g. participants are enrolled mid-study with custom categories that must be replaced), reopen then with that evidence.

For now, #974 is the fix. #973 can stay closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants