Skip to content

feat: hide courses by teacher, room, time slot or one at a time - #222

Merged
kernoeb merged 3 commits into
mainfrom
feat/course-filters
Sep 28, 2026
Merged

kernoeb merged 3 commits into
mainfrom
feat/course-filters

Conversation

@kernoeb

@kernoeb kernoeb commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Closes #216

You can now hide courses in 4 more ways, on top of the existing "hide by name" list:

  • by teacher: hides courses whose description contains a keyword, for example "pas de prof" or a surname
  • by room: hides courses whose location contains a keyword, for example "joker"
  • by weekly time slot: a weekday with a start and end time, for example Thursday 14:00–18:00 for sport. A course that overlaps the slot, even in part, is hidden
  • one course: from the event details, "Cacher ce cours" hides only that session. The old button, now "Cacher ce type de cours", still hides every course with that title

Where filtering happens

All filters run on the server, in getFormattedEvents, the same place as the blocklist. This keeps one code path, and the ICS feed hides the same courses as the app. The share modal builds the ICS link from the same filterParams as the events requests.

Data shape and sync

  • The shared shape and helpers are in packages/libs/src/event-filters.ts, used by the web app and the API: { teachers: string[], rooms: string[], slots: { day, start, end }[], hidden: { key, title, start }[] }
  • The web app stores the filters in settings.eventFilters and syncs them as one eventFilters user pref, a JSON string like customGroups. The server validates it with eventFiltersInput, and prefsMeta accepts the new key
  • Prefs are separate columns, not a JSON blob, so this adds one nullable event_filters text column (migration 0009). I added the column by hand in schemas/auth.ts because generate-better-auth also rewrote the passkey table (unrelated drift from the newer better-auth version). generate-drizzle then produced a one-line ALTER TABLE
  • The blocklist stays its own pref, so existing users and old ?blocklist= ICS links keep working

Query params

/api/plannings/:fullId and /api/ics both accept these params:

  • blocklist, teachers and rooms: comma-separated keywords, case-insensitive
  • slots: for example 4-1400-1800 (ISO weekday 1 = Monday)
  • tz: the timezone of the slots. The app sends it only when slots exist, set to the timezone the calendar shows. The default is Europe/Paris
  • hidden: comma-separated keys

The ICS cache key now includes every filter, sorted, so the same filters in any order use the same cache entry.

Why one course is hidden by a hash, not by UID

I checked real sources. UIDs are unique in every sampled planning, but Celcat UIDs end with the event's position in the file (3202836-4, 3202813-13). The suffix changes when an event is added or removed earlier in the feed, so a hide by UID would silently stop working. Instead, the key is a 32-bit FNV-1a hash of the lowercase title and the start time, 6 or 7 characters in base36. The benefits:

  • the key stays the same while the course does not move
  • it keeps ICS links short, compared with ADE UIDs of 60 or more characters
  • it also hides the duplicate copy when the same course comes from 2 plannings

When you hide a new course, the app removes hidden courses from past days, so the list stays short.

Other changes

  • TagInput now makes a unique helper id with useId(), because the settings page shows 3 of them
  • The texts in the share modal and the login screen now say "cours cachés" instead of "liste de blocage"
  • One line in AGENTS.md about the filters

Tested

  • bun run lint-fix && bun run lint && bun run typecheck && bun run test:unit: 188 pass, 0 fail
  • New tests:
    • test/event-filters.test.ts: normalising, slot encoding, query parsing, the matcher (title, teacher, room, hidden key, slot overlap, summer time, other timezone) and the cache key
    • test/web.useSettings.eventFilters.test.ts: sync encoding, query params, and removing past hidden courses
    • test/ics.test.ts and test/plannings.routes.test.ts: route tests for each filter
    • test/api.authValidators.test.ts: eventFiltersInput and the prefsMeta key
  • apps/api bun run build: the bundle picks up @libs/event-filters
  • Live check with bun dev in Chrome, in an isolated context, on IUT Nantes INFO1-G1 (Celcat):
    • one-off hide of Thursday's "CM": the request sends hidden=h8ueyb and the course disappears. It then appears in the settings, and "Réafficher" brings it back
    • teacher simonneau, room amphi and slot Wednesday 08:00–10:00: each change reloads with the right params, and the matching courses disappear. Nothing matching remains
    • the ICS link in the share modal carries every filter. curl on the feed: 231 events without filters, 203 with an old blocklist=anglais link, 165 with all filters, and no remaining "SIMONNEAU" or "Amphi"
    • the settings section works at 390 px width

Not tested live: account sync. I could not sign in through Discord or GitHub from the test browser. Sync uses the same syncPref path as customGroups, and the unit tests cover the encoding and the server validator.

🤖 Generated with Claude Code

kernoeb and others added 3 commits September 28, 2026 21:54
The blocklist only hid courses by title. Add 4 more filters, applied by
the API so the calendar and the ICS feed stay the same:

- teacher: keyword in the description (sources have no teacher field)
- room: keyword in the location
- weekly time slot: weekday and start/end time, read in the timezone
  the calendar shows (`tz`, default Europe/Paris)
- one course: hidden from the event details, keyed by a short hash of
  its title and start time, because Celcat UIDs end with the event's
  position in the file

Filters are stored in one `eventFilters` pref (JSON string, new
`event_filters` column) and synced like customGroups. The blocklist
pref and old `?blocklist=` links keep working.

Closes #216

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- match room and teacher filters on the raw text too, so "joker"
  hides "salle joker à distance" courses
- "Cacher ce cours" always hides one session, never the whole title
- slot filters ignore all-day and multi-day events
- build the ICS cache key with JSON.stringify so values cannot collide
- read the timezone in filterParams only when slots exist
- show hidden course dates in the calendar timezone
- drop the unused hasEventFilters and share parseList with ics.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kernoeb
kernoeb merged commit f0385bb into main Sep 28, 2026
9 checks passed
@kernoeb
kernoeb deleted the feat/course-filters branch September 28, 2026 20:26
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.

Proposition d'ajout de filtres sur les cours

1 participant