Central Station / YPM-FEATURE-MY-CLASSES-SORTING-FILTERS

My Classes sorting and filtering

features/my-classes-sorting-filters.md · Updated 2026-05-20
GET /api/tickets/YPM-FEATURE-MY-CLASSES-SORTING-FILTERS

Summary

Sort Students A→Z; sort Assignments by Title or Due date; filter Assignments by type

3Questions 0Links 0Comments 0PRs
Open questions 3 items
  1. 1 **URL-persistence of sort/filter state.** `?sort=name-asc&type=daily-pages,thesis` makes a filtered view shareable and back-button-friendly, at the cost of router plumbing. In-memory only is the simpler ship. Bryant picks.
  2. 2 **Filter UX shape.** Multi-select dropdown vs. a row of toggleable chips above the table. Dropdown scales better when the type list is long; chips are faster to scan when it's short. Bryant picks.
  3. 3 **Rollout flag.** Default expectation is this rides the existing `course-view` feature flag (see [course-view.md](course-view.md)) rather than introducing a second flag. Bryant to confirm.
Spec body Markdown
# My Classes sorting and filtering

Give teachers in the course view a way to sort the Students roster alphabetically, sort the Assignments table by title or due date, and filter the Assignments table by assignment type (Daily Pages, Thesis-Driven Essay, etc.) so they can find a student or assignment without scanning the whole list.

## Problem

In the redesigned course view ([course-view.md](course-view.md)) the Students tab is a flat roster and the Assignments tab is a flat table. For the pilot UA professor (150+ students across multiple sections) and any teacher with more than a handful of assignment types active in a course, "find Ada in this class" or "show me only the Daily Pages assignments" today means eyeballing the list top-to-bottom. There's no ordering guarantee (current order is whatever the loader returns) and no filter affordance at all.

The workaround is Cmd-F in the browser, which doesn't help when the teacher knows the *kind* of thing they want but not the exact name ("show me everything Daily Pages").

## Goals

- Students tab can be sorted alphabetically (A→Z and Z→A) by student name.
- Assignments tab can be sorted alphabetically (A→Z and Z→A) by assignment title.
- Assignments tab can be sorted by due date (soonest-first and latest-first).
- Assignments tab can be filtered to one or more assignment types.
- Default order is stable and predictable so teachers don't have to re-sort every time they land.

## Non-goals

- Sorting by other Students columns (submission counts, last activity, etc.). One sort axis on this page; revisit if teachers ask.
- Sorting Assignments by status counts (In Progress / Submitted / Graded / Paste). Keep the sort axes to Title and Due date for v1.
- Sorting/filtering on the top-level `/app/my-classes` class grid. That page is a small set of class cards; no scaling problem there yet.
- Saving sort/filter preferences across sessions. State is per-page-load.
- Full-text search inside a class. Filtering is by structured field (assignment type) only.
- Filtering Students by enrollment state, section, or any roster attribute beyond name sort.

## Domain notes

- **Course** — both controls scope to a single Course (the course view at `/app/my-classes/:courseId`).
- **Students tab** — sort is over the course's enrolled student list (the same roster the loader builds today).
- **Assignments tab** — sort is over the per-row assignment entries shown in the table. Filter is over the row's AssignmentType. Today each row corresponds to an AssignmentType active in the course; once [assignments-unification](assignments-unification.md) lands, the row identity may shift, but the filter axis (assignment type) stays the same.
- No state transitions; this is presentation-layer only.

## UX sketch

Controls live above each tab's table, left-aligned, not in a separate filter drawer.

**Students tab**

A single sort control (the `Name` column header acts as the toggle, with a small ↑/↓ indicator). Default is A→Z. Clicking flips to Z→A; clicking again returns to A→Z.

```
+------------------------------------------------------------------+
| [ Students ]  [ Assignments ]                                    |
+------------------------------------------------------------------+
| Name ↑                                                            |
+------------------------------------------------------------------+
| Ada Lovelace            3 docs     [View Details]                |
| Alan Turing             1 doc      [View Details]                |
| Grace Hopper            2 docs     [View Details]                |
| Linus Torvalds          0 docs     [View Details]                |
+------------------------------------------------------------------+
```

**Assignments tab**

Three controls above and within the table:

1. **Type filter** — a multi-select dropdown labeled "Type" listing the assignment types this teacher can normally assign (per [AssignmentType visibility](assignment-type-visibility.md)) that also appear in this course's assignments. So the dropdown only shows types both (a) within the teacher's normal scope and (b) actually present in the table — no empty options, no types the teacher can't see elsewhere. Default: all selected (no filter applied). A small chip count appears on the trigger when the filter is narrowed: `Type (2)`.
2. **Title sort** — the `Assignment` column header toggles A→Z / Z→A. Default A→Z.
3. **Due date sort** — the `Due` column header toggles soonest-first / latest-first.

Only one sort column is active at a time; clicking a different column header takes over as the active sort. The inactive sortable headers show a faint hint indicator that they're sortable.

```
+------------------------------------------------------------------+
| [ + Create New Assignment ]                                      |
+------------------------------------------------------------------+
| [ Students ]  [ Assignments ]                                    |
+------------------------------------------------------------------+
| [ Type ▾ ]                                                       |
+------------------------------------------------------------------+
| Assignment ↑       Due ⇅    In Prog  Submitted  Graded  Paste    |
| Daily Pages — Wk 3 May 14   8        [2]        [0]     [0]      |
| Macbeth essay      May 10   11       [3]        [1]     [0]      |
| Reading reflection May 5    0        0          0       0        |
+------------------------------------------------------------------+

  Type filter open:
  +-----------------------------+
  | [x] Daily Pages             |
  | [x] Thesis-Driven Essay     |
  | [x] 5-Paragraph Essay       |
  | [ ] DBQ                     |
  | [ ] LEQ                     |
  |                             |
  | [ Clear ]     [ Apply ]     |
  +-----------------------------+
```

**Empty / edge states**

- Type filter applied, no rows match: empty state with "No assignments match this filter. [Clear filter]"
- Course has zero assignments: existing empty state from `course-view.md` wins; no filter control rendered.
- Course has zero students: existing empty state wins; no sort control rendered.

**Locale-aware sort**

Use the browser's locale-aware comparator (`Intl.Collator`) so "Ángela" sorts where a Spanish-speaking teacher expects, not at the end after "Z".

## Data model implications

None. All three controls are presentation-layer over data the loaders already return.

- Sort can happen client-side after the loader returns; the lists in question are bounded (hundreds of students max, dozens of assignments) and don't need a server round-trip.
- Filter can also happen client-side. A future move to URL-param-driven state (so a filtered view is shareable) is plausible but out of scope here.

No migrations, no new fields, no indexes.

## File paths in `yawp-2.0` likely to change

- `services/web-app/app/routes/app.my-classes.$courseId/components/students-table.tsx` — add sort toggle on Name header
- `services/web-app/app/routes/app.my-classes.$courseId/components/assignments-table.tsx` — add Type filter dropdown and sort toggle on Assignment header
- `services/web-app/app/routes/app.my-classes.$courseId/route.tsx` — loader unchanged; route may need to surface the list of types active in the course (or compute it client-side from the assignments list)
- Possibly a shared sort/filter component (`services/web-app/app/components/...`) if the column-header-sort pattern is reusable

## Open questions (Bryant's call before building)

These are engineering judgement calls — product is fine with whichever way Bryant lands.

- [ ] **URL-persistence of sort/filter state.** `?sort=name-asc&type=daily-pages,thesis` makes a filtered view shareable and back-button-friendly, at the cost of router plumbing. In-memory only is the simpler ship. Bryant picks.
- [ ] **Filter UX shape.** Multi-select dropdown vs. a row of toggleable chips above the table. Dropdown scales better when the type list is long; chips are faster to scan when it's short. Bryant picks.
- [ ] **Rollout flag.** Default expectation is this rides the existing `course-view` feature flag (see [course-view.md](course-view.md)) rather than introducing a second flag. Bryant to confirm.

Scope note (not an open question, recorded so it doesn't get re-litigated): "My Classes view" in the original ask refers to the inside-a-class course view (`/app/my-classes/:courseId`), not the top-level `/app/my-classes` class grid.

## Edge cases

- Two students with the same first name: secondary sort on last name (or full display name) to keep order stable.
- Two assignments with identical titles: secondary sort on due date (ascending) so the order is deterministic. When the active sort *is* due date, secondary sort on title.
- Assignments with no due date set: group them at the end of the due-date sort regardless of direction (so the "no due date" pile doesn't bounce to the top when the teacher flips to latest-first). Show the cell as `—` rather than blank.
- Two assignments with the same due date: secondary sort on title.
- Assignment type names with accented characters or non-Latin scripts: use locale-aware comparison.
- Teacher narrows the Type filter to a single type, then creates a new assignment of a different type: the new assignment is hidden by the filter. Consider showing a one-time inline notice ("New assignment created — not shown by current filter. [Clear filter]") rather than silently hiding it.
- Re-sorting while a drilldown is open: drilldown is unaffected (it's a separate view); when the teacher returns, the sort state is preserved.
- Mobile / narrow viewport: sort toggles in column headers must remain tappable; the Type filter dropdown should stack below the tab bar rather than overflow horizontally.

## Test plan

Unit:
- Sort comparator: locale-aware, case-insensitive, stable secondary sort on tiebreakers (last name for students; due date for title-sorted assignments; title for due-date-sorted assignments).
- Due-date comparator: assignments with no due date sink to the bottom for both directions.
- Filter predicate: empty selection treated as "all" (not "none"); narrowing the selection returns only rows whose assignment type matches.

Integration:
- Students tab renders Name header with sort indicator; clicking flips order; clicking again restores.
- Assignments tab renders Type dropdown listing only types active in this course; selecting a subset filters the table; clearing restores the full list.
- Assignments title sort works independently of the type filter (sort + filter compose correctly).
- Empty-state rendering when a filter excludes all rows.

Manual QA:
- UA professor account (or a seeded test course with 150+ students and 10+ assignment types): roster sort is snappy, dropdown lists all active types in alphabetical order.
- Default landing on the Students tab shows A→Z order without the teacher clicking anything.
- Default landing on the Assignments tab shows A→Z by title with all types selected.
- Clicking the Due column header switches the active sort to due date soonest-first; clicking again flips to latest-first.
- Assignments with no due date appear at the bottom of both due-date orderings.
- Filter + sort compose: select two types, sort Z→A by title, confirm only those two types are shown in reverse alphabetical order. Repeat with the due-date sort.
- Locale check: a student named "Ángela Ruiz" sorts before "Beatriz", not after "Zoe".

## Rollout

Small UI-only change with no data model risk. Default plan: piggyback on the existing `course-view` feature flag rather than introducing a second one, and ship to the same pilot order (UA → Washington → Birmingham City → broader). Bryant confirms the flag plumbing in the open-questions section above.

## Engineering handoff checklist

- [x] Domain context covered
- [x] File paths in `yawp-2.0` listed
- [x] Data model implications spelled out, including backward-compat plan
- [x] UX sketch in prose
- [x] Edge cases enumerated
- [x] Test plan written
- [x] Rollout plan decided (ride `course-view` flag; Bryant confirms plumbing)
Repo sync Not recorded

No repo sync metadata recorded yet.