Skip to content

perf(sessions): session list and context have no ETag, so unchanged data is re-transferred on every load #803

Description

@YsDirector

Problem

/api/sessions and /api/sessions/[id] never send ETag/Last-Modified, and the list route also sends Cache-Control: no-store. A client with an unchanged session store therefore re-transfers the full payload on every page load, session switch, and app resume.

This is most visible on mobile. An installed PWA or a WebView shell does not keep the SPA alive across launches, so the app re-mounts on every open and re-fetches the session list plus the active session's context window each time. Measured on a local server with 82 stored sessions:

request body after #731 (gzip)
GET /api/sessions 95,446 B 27,625 B
GET /api/sessions/{id}?deferThinking=1&deferMedia=1 (8,712-message session, tail=50 page) 257,987 B 100,591 B

gzip (#731) removed ~71%, but nothing lets the client skip a request whose answer has not changed:

$ curl -D - -H 'If-None-Match: "anything"' .../api/sessions
HTTP/1.1 200 OK
cache-control: no-store
content-encoding: gzip

Why this should be cheap to fix

The server already computes exactly what a validator needs:

  • app/api/sessions/route.ts returns sessionListVersion (getSessionListVersion()globalThis.__piSessionListGeneration), a monotonic counter the client already uses in components/SessionSidebar.tsx to decide whether to refresh.
  • app/api/sessions/[id]/route.ts already calls statSync(filePath).mtime to populate the modified field.

So a weak ETag is derivable on both routes without adding I/O.

A wrinkle: no-store blocks revalidation

The routes forbid storing the response, and the client requests the list with fetch(..., { cache: "no-store" }) in two places. A browser that may not store a response cannot send If-None-Match, so adding an ETag alone would change nothing. Making 304s reachable means relaxing these to no-cache (store, but always revalidate). That is a real API behavior change, so I am opening an issue rather than sending a PR.

Related: two call sites pull the whole list for one session

  • components/AppShell.tsx:570 (restoreWorkspaceContext) — finds the last-open session id.
  • components/AppShell.tsx:768 (hydrateSelectedSession) — hydrates one transient session.

Conditional requests would make both free when nothing changed; a targeted lookup (e.g. ?id=) would avoid the transfer entirely.

Proposal

  1. Add a weak ETag to /api/sessions and /api/sessions/[id]; reply 304 to a matching If-None-Match. Keep ?force=1 bypassing validators.
  2. Relax those routes from no-store to no-cache so browsers may store and revalidate.
  3. Optionally accept a single-session lookup on /api/sessions for the two call sites above.

Happy to prepare a PR for (1) and (2) if the direction looks right. Point 3 and the no-store relaxation are separable if you would rather take them one at a time.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions