Configurable ignore rules for unwanted content (e.g. sports) #20

Closed
opened 2026-09-11 22:28:35 +00:00 by agent-pi · 3 comments
Collaborator

Problem

I am totally uninterested in some topics (e.g. any type of sports) and don't want to see groups / articles about them, but the reader shows everything that gets aggregated.

Goal

  • Implement configurable "ignore" rules so that unwanted content can be excluded from the reader
## Problem I am totally uninterested in some topics (e.g. any type of sports) and don't want to see groups / articles about them, but the reader shows everything that gets aggregated. ## Goal - Implement configurable "ignore" rules so that unwanted content can be excluded from the reader
Author
Collaborator

Findings

  • Closest existing pattern: the tag ignore list on the settings page (tags DB tables) — this feature is the content-level sibling of that.
  • Design questions:
    • Filter point: at aggregation time (drop/never create groups) vs display time (filter in the reader)? Aggregation-time keeps the reader clean but loses content irreversibly; display-time is reversible.
    • What do rules match on: tags (already extracted per article), keywords in titles, feed URL?

Plan of attack

  1. Design pass first (filter point + rule model) — comment here before coding.
  2. Rule storage + settings UI, extending the existing settings page (naturally composes with #8's settings store, but the existing tag tables may suffice initially — no hard dependency).
  3. Filtering implementation + tests.
## Findings - Closest existing pattern: the tag ignore list on the settings page (tags DB tables) — this feature is the content-level sibling of that. - Design questions: - Filter point: at aggregation time (drop/never create groups) vs display time (filter in the reader)? Aggregation-time keeps the reader clean but loses content irreversibly; display-time is reversible. - What do rules match on: tags (already extracted per article), keywords in titles, feed URL? ## Plan of attack 1. Design pass first (filter point + rule model) — comment here before coding. 2. Rule storage + settings UI, extending the existing settings page (naturally composes with #8's settings store, but the existing tag tables may suffice initially — no hard dependency). 3. Filtering implementation + tests.
Author
Collaborator

Design decisions

Filter point: display time. Aggregation keeps collecting everything; the reader (and only the reader) hides what matches. Reversible — rule tweaks apply instantly, nothing is destroyed — and it composes with the existing hide_read filter in the same place.

Rule model: case-insensitive keyword rules. One rule per line, stored in the settings table (key ignore_rules, newline-joined) via the #8 settings mechanism. A group is hidden when any rule matches a substring of any member article title or any of the groups tags. Tags cover curated cases (sport), titles catch untagged ones. Group-level only: a matching group disappears whole; individual articles inside non-matching groups stay.

Scope note: direct URLs to ignored groups still work, and the g next-unread navigation will skip ignored groups so it never lands on hidden content.

Plan of attack

  1. services/ignore_rules.py: normalize_rules (split/strip/dedupe) + matches_any_rule — pure functions, unit-tested.
  2. Settings: POST /settings/ignore-rules + "Ignore rules" card with textarea on the settings page.
  3. Reader: index() filters ignored groups; next-unread (g) loops get_next_aggregate_with_unread past ignored ones.

Branch: feat/issue-20-ignore-rules. Commits: (1) :sparkles: Add ignore rules service, (2) :sparkles: Add ignore rules setting with settings-page editor, (3) :sparkles: Filter ignored aggregates from the reader and next-unread navigation.

## Design decisions **Filter point: display time.** Aggregation keeps collecting everything; the reader (and only the reader) hides what matches. Reversible — rule tweaks apply instantly, nothing is destroyed — and it composes with the existing `hide_read` filter in the same place. **Rule model: case-insensitive keyword rules.** One rule per line, stored in the settings table (key `ignore_rules`, newline-joined) via the #8 settings mechanism. A group is hidden when any rule matches a substring of any member article title or any of the groups tags. Tags cover curated cases (`sport`), titles catch untagged ones. Group-level only: a matching group disappears whole; individual articles inside non-matching groups stay. **Scope note:** direct URLs to ignored groups still work, and the `g` next-unread navigation will skip ignored groups so it never lands on hidden content. ## Plan of attack 1. `services/ignore_rules.py`: `normalize_rules` (split/strip/dedupe) + `matches_any_rule` — pure functions, unit-tested. 2. Settings: POST `/settings/ignore-rules` + "Ignore rules" card with textarea on the settings page. 3. Reader: `index()` filters ignored groups; next-unread (`g`) loops `get_next_aggregate_with_unread` past ignored ones. Branch: `feat/issue-20-ignore-rules`. Commits: (1) `:sparkles: Add ignore rules service`, (2) `:sparkles: Add ignore rules setting with settings-page editor`, (3) `:sparkles: Filter ignored aggregates from the reader and next-unread navigation`.
Author
Collaborator

Implemented in PR #31 (merged as dd1b0e1): display-time ignore rules — one keyword per line (Settings → Ignore Rules), matching group article titles and tags case-insensitively; reader index and g next-unread navigation skip matching groups. Aggregation untouched.

Implemented in PR #31 (merged as dd1b0e1): display-time ignore rules — one keyword per line (Settings → Ignore Rules), matching group article titles and tags case-insensitively; reader index and `g` next-unread navigation skip matching groups. Aggregation untouched.
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
marvin8/yunjin#20
No description provided.