7.7 KiB
Design: Session Archive, Sunday Badge, Multi-Select, and Pagination
Overview
Four enhancements to the History page and Session Detail page:
- Archive/Unarchive sessions — hide sessions from the default history list, with a filter to view archived sessions
- Sunday "Game Night" badge — visual indicator on session cards when a session took place on a Sunday
- Multi-select mode — bulk archive and delete operations on the History page (admin only)
- 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:
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 = 1on 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 = 0on 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
archiveanddelete: 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
- For
- Validation: Returns 400 if
idsis empty, ifactionis not one of the three valid values, or ifidsis not an array - Response:
{ success: true, affected: <count> } - Route registration: Must be registered before
/:idroutes to avoid Express matching"bulk"as an:idparameter
Modified Endpoint
GET /api/sessions
Add two query parameters:
filter:"default"|"archived"|"all""default"(when omitted): returns sessions wherearchived = 0"archived": returns sessions wherearchived = 1"all": returns all sessions regardless of archived status
limit:"5"|"10"|"25"|"50"|"all""5"(when omitted): returns the first 5 sessions (ordered bycreated_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 includearchivedonce the column existsPOST /api/sessions— unchanged (new sessions default toarchived = 0)POST /api/sessions/:id/close— unchangedDELETE /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:
- Filter dropdown: "Sessions" (default,
filter=default), "Archived" (filter=archived), "All" (filter=all) - Show dropdown: 5 (default), 10, 25, 50, All
- Session count: "N sessions total" — derived from the
X-Total-Countresponse header - 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" ifsession.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:
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)