Skip to content

fix(categories): keep Top Categories colors when category query contains encoded segments#781

Merged
ErikBjare merged 3 commits intoActivityWatch:masterfrom
TimeToBuildBob:fix/top-categories-encoded-color-1135-v2
Mar 22, 2026
Merged

fix(categories): keep Top Categories colors when category query contains encoded segments#781
ErikBjare merged 3 commits intoActivityWatch:masterfrom
TimeToBuildBob:fix/top-categories-encoded-color-1135-v2

Conversation

@TimeToBuildBob
Copy link
Copy Markdown
Contributor

@TimeToBuildBob TimeToBuildBob commented Mar 4, 2026

Summary

Fixes category color fallback in Top Categories when category segments are URL-encoded (e.g. spaces encoded as %20).

Issue: ActivityWatch/activitywatch#1135

Root cause

Top Categories (aw-summary) resolves colors via categoryStore.get_category_color(e.data['$category']).
After navigation/filtering through query params, category segments can be URL-encoded in some paths (Work%20Project), while configured categories are stored decoded (Work Project).
The lookup then misses, falls back to Uncategorized, and renders gray.

Changes

  • src/stores/categories.ts
    • Normalize/decodeURIComponent each category segment in get_category_color() before lookup.
    • Keep behavior safe by catching decode errors and using the original segment.
  • test/unit/store/categories.test.node.ts
    • Add regression test proving encoded and decoded category segments resolve to the same configured color.

Validation

  • npm test -- test/unit/store/categories.test.node.ts

(Repo-level lint in this environment can conflict with parent worktree eslint plugin resolution; targeted unit test is green.)


Important

Fixes category color resolution by decoding URL-encoded segments in get_category_color() and adds a test for this behavior.

  • Behavior:
    • Fixes category color resolution in get_category_color() in src/stores/categories.ts by decoding URL-encoded segments before lookup.
    • Falls back to original segment if decoding fails.
  • Tests:
    • Adds test get_category_color decodes URL-encoded category segments in categories.test.node.ts to verify encoded and decoded segments resolve to the same color.

This description was created by Ellipsis for 09fc232. You can customize this summary. It will automatically update as commits are pushed.

Copy link
Copy Markdown
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed everything up to 09fc232 in 6 seconds. Click for details.
  • Reviewed 48 lines of code in 2 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_qeGfzLiQW7N4q3Iv

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 4, 2026

Codecov Report

❌ Patch coverage is 85.71429% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 25.92%. Comparing base (137ee09) to head (f9f85a3).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
src/stores/categories.ts 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #781      +/-   ##
==========================================
+ Coverage   25.71%   25.92%   +0.21%     
==========================================
  Files          30       30              
  Lines        1754     1759       +5     
  Branches      307      307              
==========================================
+ Hits          451      456       +5     
  Misses       1281     1281              
  Partials       22       22              

☔ View full report in Codecov by Sentry.
📢 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
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR fixes a color-resolution bug in the Top Categories (aw-summary) view where category segments arriving URL-encoded from route query params (e.g. Work%20Project) failed to match the stored decoded names (e.g. Work Project), causing a silent fallback to the gray "Uncategorized" color.

Changes:

  • src/stores/categories.ts: get_category_color() and get_category_score() now run each segment through decodeURIComponent (with a try/catch fallback for malformed sequences) before passing to the store lookup.
  • test/unit/store/categories.test.node.ts: Two new regression tests confirm that encoded and decoded segments resolve to the same configured color and score.

Minor note: The normalization block is duplicated across the two getters — extracting it to a shared helper would improve maintainability (see inline comment), but it does not affect correctness.

Confidence Score: 5/5

  • Safe to merge — targeted fix with solid test coverage and no risk to existing behaviour.
  • The change is minimal and well-scoped: only the two color/score lookup paths are touched, the fallback (original segment) is preserved on decode failure, and both new regression tests pass. The only open item is a style-level duplication, which is non-blocking.
  • No files require special attention.

Important Files Changed

Filename Overview
src/stores/categories.ts Adds decodeURIComponent normalization (with try/catch fallback) to get_category_color and get_category_score before the category lookup, fixing gray-color fallback when route query params carry URL-encoded category segments. Minor duplication between the two getters.
test/unit/store/categories.test.node.ts Adds two regression tests — one for get_category_color and one for get_category_score — verifying that encoded (Work%20Project) and decoded (Work Project) segments resolve to the same configured value.

Sequence Diagram

sequenceDiagram
    participant UI as aw-summary (Vue component)
    participant Store as categoryStore
    participant Helper as getColorFromCategory

    UI->>Store: get_category_color(["Work%20Project"])
    Note over Store: decodeURIComponent each segment<br/>→ ["Work Project"]
    Store->>Store: get_category(["Work Project"])
    Store-->>Store: returns matched Category
    Store->>Helper: getColorFromCategory(category, classes)
    Helper-->>Store: "#123456"
    Store-->>UI: "#123456" ✅

    Note over UI,Store: Before fix — encoded segment missed lookup
    UI->>Store: get_category_color(["Work%20Project"])
    Store->>Store: get_category(["Work%20Project"])
    Store-->>Store: no match → Uncategorized fallback
    Store->>Helper: getColorFromCategory(Uncategorized, classes)
    Helper-->>Store: gray ❌
Loading

Comments Outside Diff (1)

  1. src/stores/categories.ts, line 115-126 (link)

    Duplicated normalization logic

    The URL-decoding normalization block is copy-pasted verbatim in both get_category_color and get_category_score. Extracting it to a small helper keeps the intent in one place and makes future changes easier:

    function normalizeSegments(cat: string[]): string[] {
      return (cat || []).map(segment => {
        try {
          return decodeURIComponent(segment);
        } catch {
          return segment;
        }
      });
    }

    Then both getters just call normalizeSegments(cat) before passing to their respective helpers.

    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!

Reviews (2): Last reviewed commit: "fix(categories): apply URL-decode normal..." | Re-trigger Greptile

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 4, 2026

Additional Comments (1)

src/stores/categories.ts, line 119
get_category_score has the same URL-encoding vulnerability as get_category_color had. It calls this.get_category(cat) directly without decoding, so a category passed as ['Work%20Project'] will fail to match the stored ['Work Project'] and silently return a score of 0.

For consistency and robustness, apply the same normalization:

    get_category_score() {
      return (cat: string[]): number => {
        const normalized = (cat || []).map(segment => {
          try {
            return decodeURIComponent(segment);
          } catch {
            return segment;
          }
        });
        return getScoreFromCategory(this.get_category(normalized), this.classes);
      };
    }

Alternatively—and more robustly—move the normalization into get_category() itself so all future callers benefit automatically without duplicating the logic.

@ErikBjare
Copy link
Copy Markdown
Member

@TimeToBuildBob Worth addressing those greptile comments

Same URL-encoding vulnerability existed in get_category_score as was
fixed in get_category_color. Encoded segments like 'Work%20Project'
would fail to match stored decoded names and silently return score 0.

Adds a regression test mirroring the one for get_category_color.
@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

Addressed the Greptile concern: applied the same URL-decode normalization to get_category_score() (cf2f72f).

Also added a regression test mirroring the one for get_category_color.

@ErikBjare
Copy link
Copy Markdown
Member

@greptileai review

@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

Also extracted the normalization logic to a normalizeSegments helper (f9f85a3) to eliminate the duplication Greptile flagged. Both getters now call it instead of repeating the decodeURIComponent map block.

@ErikBjare ErikBjare merged commit f2d0396 into ActivityWatch:master Mar 22, 2026
8 checks passed
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