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

7.7 KiB

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:

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:

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)