Files
jackboxpartypack-gamepicker/docs/superpowers/specs/2026-03-23-session-archive-multiselect-design.md

186 lines
7.7 KiB
Markdown
Raw Normal View History

# Design: Session Archive, Sunday Badge, Multi-Select, and Pagination
## Overview
Four enhancements to the History page and Session Detail page:
1. **Archive/Unarchive sessions** — hide sessions from the default history list, with a filter to view archived sessions
2. **Sunday "Game Night" badge** — visual indicator on session cards when a session took place on a Sunday
3. **Multi-select mode** — bulk archive and delete operations on the History page (admin only)
4. **Pagination options** — configurable number of sessions shown (5, 10, 25, 50, All)
## Backend
### Schema Change
Add `archived INTEGER DEFAULT 0` to the `sessions` table using the existing defensive `ALTER TABLE` pattern in `database.js`:
```js
try {
db.exec(`ALTER TABLE sessions ADD COLUMN archived INTEGER DEFAULT 0`);
} catch (err) {
// Column already exists
}
```
No new tables. No migration file.
### New Endpoints
#### `POST /api/sessions/:id/archive`
- **Auth:** Required
- **Action:** Sets `archived = 1` on the session
- **Constraints:** Returns 400 if the session is still active (`is_active = 1`). Returns 404 if session not found.
- **Response:** `{ success: true }`
#### `POST /api/sessions/:id/unarchive`
- **Auth:** Required
- **Action:** Sets `archived = 0` on the session
- **Constraints:** Returns 404 if session not found.
- **Response:** `{ success: true }`
#### `POST /api/sessions/bulk`
- **Auth:** Required
- **Action:** Performs a bulk operation on multiple sessions
- **Body:** `{ "action": "archive" | "unarchive" | "delete", "ids": [1, 2, 3] }`
- **Constraints:**
- For `archive` and `delete`: rejects request with 400 if any session ID in the list is still active, returning the offending IDs in the error response
- All IDs must exist (404 if any are not found)
- Runs inside a database transaction — all-or-nothing
- **Validation:** Returns 400 if `ids` is empty, if `action` is not one of the three valid values, or if `ids` is not an array
- **Response:** `{ success: true, affected: <count> }`
- **Route registration:** Must be registered before `/:id` routes to avoid Express matching `"bulk"` as an `:id` parameter
### Modified Endpoint
#### `GET /api/sessions`
Add two query parameters:
- **`filter`**: `"default"` | `"archived"` | `"all"`
- `"default"` (when omitted): returns sessions where `archived = 0`
- `"archived"`: returns sessions where `archived = 1`
- `"all"`: returns all sessions regardless of archived status
- **`limit`**: `"5"` | `"10"` | `"25"` | `"50"` | `"all"`
- `"5"` (when omitted): returns the first 5 sessions (ordered by `created_at DESC`)
- `"all"`: no limit applied
- Any other value: applied as SQL `LIMIT`
The response shape stays the same (array of session objects). Each session now includes the `archived` field (0 or 1). The existing `has_notes` and `notes_preview` fields continue as-is.
**Total count:** The response should also include a way for the frontend to know how many sessions match the current filter (for the "N sessions total" display). Two options: a response header, or wrapping the response. To avoid breaking the existing array response shape, add a custom response header `X-Total-Count` with the total matching count before limit is applied.
### Unchanged Endpoints
- `GET /api/sessions/:id` — already returns the full session object; will naturally include `archived` once the column exists
- `POST /api/sessions` — unchanged (new sessions default to `archived = 0`)
- `POST /api/sessions/:id/close` — unchanged
- `DELETE /api/sessions/:id` — unchanged (still works, used by detail page)
- `PUT /api/sessions/:id/notes`, `DELETE /api/sessions/:id/notes` — unchanged
## Frontend
### History Page — Controls Bar
Replace the existing "Show All / Show Recent" toggle with a cohesive controls bar containing:
1. **Filter dropdown:** "Sessions" (default, `filter=default`), "Archived" (`filter=archived`), "All" (`filter=all`)
2. **Show dropdown:** 5 (default), 10, 25, 50, All
3. **Session count:** "N sessions total" — derived from the `X-Total-Count` response header
4. **Select button** (admin only): toggles multi-select mode on/off
Both the Filter and Show selections are persisted in `localStorage`:
- `history-filter` — stores the selected filter value (`"default"`, `"archived"`, `"all"`)
- `history-show-limit` — stores the selected limit value (`"5"`, `"10"`, `"25"`, `"50"`, `"all"`)
Values are read on mount and written on change. The frontend should always pass both `filter` and `limit` query params explicitly when calling `GET /api/sessions`, including in the 3-second polling interval.
### History Page — Session Cards
#### Sunday Badge
Sessions whose `created_at` falls on a Sunday (in the user's local timezone) display:
- An amber "GAME NIGHT" badge (with sun icon) next to the session number
- "· Sunday" appended to the date line in muted text
Determination is client-side: `parseUTCTimestamp(session.created_at).getDay() === 0` using the existing `dateUtils.js` helper.
#### Archived Badge
When viewing the "All" or "Archived" filter, archived sessions show a gray "Archived" badge next to the session number.
#### Notes Preview
Continues as-is — indigo left-border teaser when `has_notes` is true.
### History Page — Multi-Select Mode
**Entering multi-select:**
- Click the "Select" toggle button in the controls bar (admin only)
- Long-press (500ms+) on a closed session card (admin only) — enters multi-select and selects that card
**While in multi-select:**
- Checkboxes appear on each session card
- Active sessions (`is_active = 1`) are greyed out with a disabled checkbox — not selectable
- Clicking a card toggles its selection (instead of navigating to detail page)
- The "End Session" button on active session cards is hidden
- A floating action bar appears at the bottom with:
- Left: "N selected" count
- Right: context-aware action buttons:
- **"Sessions" filter:** Archive + Delete
- **"Archived" filter:** Unarchive + Delete
- **"All" filter:** Archive + Unarchive + Delete
- The action bar only appears when at least 1 session is selected
- **Delete** always shows a confirmation modal: "Delete N sessions? This cannot be undone."
- **Archive/Unarchive** execute immediately (non-destructive, reversible) with a toast confirmation
**Changing filter or limit while in multi-select:**
- Clears all selections (since the visible session list changes)
- Stays in multi-select mode
**Exiting multi-select:**
- Click the "Done" / Select toggle button
- Clears all selections and hides checkboxes + action bar
### Session Detail Page
#### Archive/Unarchive Button
- Placed alongside the existing Delete Session button at the bottom of the page
- Only visible to authenticated admins, only for closed sessions
- Shows "Archive" if `session.archived === 0`, "Unarchive" if `session.archived === 1`
- Executes immediately with toast confirmation (no modal — it's reversible)
#### Archived Banner
- When viewing an archived session, a subtle banner appears at the top of the detail content: "This session is archived"
- Includes an inline "Unarchive" button (admin only)
#### Sunday Badge
- Same amber "GAME NIGHT" badge shown next to "Session #N" in the page header
- "· Sunday" in the date/time display
### Frontend Utilities
Add a helper to `dateUtils.js`:
```js
export function isSunday(sqliteTimestamp) {
return parseUTCTimestamp(sqliteTimestamp).getDay() === 0;
}
```
## What's NOT Changing
- **Database:** No new tables, just one column addition
- **Session notes** (view/edit/delete) — untouched
- **EndSessionModal** — untouched
- **WebSocket events** — no archive-related real-time updates
- **Other routes/pages** (Home, Picker, Manager, Login) — untouched
- **`POST /sessions/:id/close`** — untouched
- **New dependencies** — none required (no new npm packages)