Files
timetracker/common
Claude ba9b92d419 Align set-criterion modifiers with Stash (any/all/none) and harmonize EXCLUDES
Closes #10.

Backend (common/criteria.py):
- Treat `excludes` as an always-orthogonal AND'd negative across both
  MultiCriterion and ChoiceCriterion; the modifier now governs only the
  `value` (include) set. This removes the prior divergence where
  MultiCriterion.EXCLUDES dropped the excludes list and ChoiceCriterion.EXCLUDES
  swapped include/exclude into a positive.
- Fold INCLUDES / INCLUDES_ALL / EXCLUDES (+ EQUALS/NOT_EQUALS aliases) into the
  shared _SetCriterion base so the two subclasses cannot drift; remove _extra_q.

M2M "has all" (games/filters.py):
- PurchaseFilter._games_to_q builds a pk__in subquery with one join per value so
  INCLUDES_ALL on the many-to-many games field works in a single .filter()
  (a naive Q(games=a) & Q(games=b) collapses to one join and matches nothing).

UI (FilterSelect + filter_bar.js):
- Add an optional any/all/none match-mode <select> (INCLUDES/INCLUDES_ALL/
  EXCLUDES) rendered before the pills via a new `leading` slot on the shared
  combobox shell. A native control so its value is its state. readSearchSelect
  serialises it to data-match; filter_bar folds it into the criterion modifier.
  Orthogonal to the (Any)/(None) presence pseudo-options and the exclude channel.
- Enable it for the M2M Purchase.games field (INCLUDES_ALL is only meaningful
  for multi-valued relations). Styled with already-compiled utilities.

Tests: harmonized EXCLUDES + INCLUDES_ALL for both criterion types, a DB-backed
INCLUDES_ALL vs INCLUDES contrast on Purchase.games, and FilterSelect /
PurchaseFilterBar rendering + round-trip of the match mode.

https://claude.ai/code/session_01KwVrGFbq13mZdhDL9G6zhg
2026-06-09 08:47:20 +02:00
..
2023-01-20 13:37:46 +00:00
2026-06-06 13:14:55 +02:00
2026-06-06 13:14:55 +02:00
2026-06-06 08:15:19 +02:00