Recents: last 15 opened/downloaded files as a sidebar view #293

Closed
opened 2026-08-20 19:41:22 +00:00 by Cordy · 2 comments
Owner

Request (2026-08-20, Nikola): a "Recents" quicklink in the left navbar for the last 10–15 files the user opened/downloaded.

Design (server-side, per-user — follows across devices):

  • New internal/recents store on the statestore pattern (clone of notify): map[user][]entry{Path, Name, At}; Record(user, path, name) dedupes to front and caps at 15; atomic JSON at <state dir>/recents.json, encrypted like the other runtime state.
  • Recorded in the raw-download handler (GET /api/v1/files/raw) — the single path the web UI uses for both opening and downloading a file. Nil-safe field on the API handler; wired unconditionally in main (not inside the peering block). WebDAV opens are out of scope v1 (sync clients would flood the list with automatic reads).
  • GET /api/v1/recents: the caller's list, existence-filtered at read time via a scoped Stat (≤15 stats, cheap) so deleted/moved files silently drop out; entries return the fresh FileInfo (size, modTime) so rows render like any listing.
  • UI: Recents nav entry (clock icon) directly under Personal, always visible; a files-style view ordered newest-opened first with the path subline (renderRows + showPath), rows fully functional (open = re-records). Custom empty state ("Nothing opened yet").

Privacy note: the list is the user's own activity, visible only to them via their own endpoint; admins do not get a cross-user surface here (the audit log already covers governance needs).

Mockup-first per house process.

**Request (2026-08-20, Nikola):** a "Recents" quicklink in the left navbar for the last 10–15 files the user opened/downloaded. **Design (server-side, per-user — follows across devices):** - New `internal/recents` store on the statestore pattern (clone of `notify`): `map[user][]entry{Path, Name, At}`; `Record(user, path, name)` dedupes to front and caps at **15**; atomic JSON at `<state dir>/recents.json`, encrypted like the other runtime state. - **Recorded in the raw-download handler** (`GET /api/v1/files/raw`) — the single path the web UI uses for both opening and downloading a file. Nil-safe field on the API handler; wired unconditionally in main (not inside the peering block). WebDAV opens are out of scope v1 (sync clients would flood the list with automatic reads). - `GET /api/v1/recents`: the caller's list, **existence-filtered at read time** via a scoped `Stat` (≤15 stats, cheap) so deleted/moved files silently drop out; entries return the fresh FileInfo (size, modTime) so rows render like any listing. - UI: **Recents** nav entry (clock icon) directly under Personal, always visible; a files-style view ordered newest-opened first with the path subline (`renderRows` + `showPath`), rows fully functional (open = re-records). Custom empty state ("Nothing opened yet"). **Privacy note:** the list is the user's own activity, visible only to them via their own endpoint; admins do not get a cross-user surface here (the audit log already covers governance needs). Mockup-first per house process.
Author
Owner

Shipped as v0.6.56 (PR #294), live on both dogfoods.

What changed:

  • New Recents entry in the sidebar (clock icon, directly under Personal): the last 15 files you opened or downloaded through the web UI, newest first.
  • Server-side and per-user — the list follows you across browsers/devices; nobody else sees yours. Stored beside the other runtime state (recents.json), so it survives redeploys.
  • Each entry is re-checked against your access on view: deleted, moved, or revoked files silently drop off. Opening the same file again moves it to the top (no duplicates).
  • WebDAV traffic is deliberately not recorded, so a Finder/Explorer mount can't flood the list.

Dogfood checklist:

  • Open/download a few files → they appear in Recents, newest first
  • Re-open an older one → it jumps to the top, no duplicate
  • Open 16+ distinct files → list caps at 15
  • Delete (trash) a listed file → it disappears from Recents on next view
  • Log in from a second browser → same list (server-side)
  • Second user (e.g. nikola-test on bao) sees only their own list
  • Empty state shows the "Nothing opened yet…" message
  • Browse files via WebDAV mount → those do NOT appear in Recents
Shipped as **v0.6.56** (PR #294), live on both dogfoods. **What changed:** - New **Recents** entry in the sidebar (clock icon, directly under Personal): the last 15 files you opened or downloaded through the web UI, newest first. - Server-side and per-user — the list follows you across browsers/devices; nobody else sees yours. Stored beside the other runtime state (`recents.json`), so it survives redeploys. - Each entry is re-checked against your access on view: deleted, moved, or revoked files silently drop off. Opening the same file again moves it to the top (no duplicates). - WebDAV traffic is deliberately **not** recorded, so a Finder/Explorer mount can't flood the list. **Dogfood checklist:** - [ ] Open/download a few files → they appear in Recents, newest first - [ ] Re-open an older one → it jumps to the top, no duplicate - [ ] Open 16+ distinct files → list caps at 15 - [ ] Delete (trash) a listed file → it disappears from Recents on next view - [ ] Log in from a second browser → same list (server-side) - [ ] Second user (e.g. nikola-test on bao) sees only their own list - [ ] Empty state shows the "Nothing opened yet…" message - [ ] Browse files via WebDAV mount → those do NOT appear in Recents
Author
Owner

Dogfood verified by Nikola on both instances (v0.6.56) — Recents behaves as designed. Closing.

Solution summary: new internal/recents statestore (atomic JSON via statestore, per-user, cap 15, case-insensitive dedupe-to-front), recorded in the raw-download handler only (WebDAV deliberately excluded). GET /api/v1/recents re-Stats each path through the caller's scoped store so vanished/revoked entries self-clean. Sidebar entry (clock icon) + files-style view, i18n ×4. Wired via the narrow RecentsStore interface to keep internal/api import-clean. Covered by TestRecordDedupeCapOrder / TestPersistenceRoundTrip.

Dogfood verified by Nikola on both instances (v0.6.56) — Recents behaves as designed. Closing. **Solution summary:** new `internal/recents` statestore (atomic JSON via `statestore`, per-user, cap 15, case-insensitive dedupe-to-front), recorded in the raw-download handler only (WebDAV deliberately excluded). `GET /api/v1/recents` re-Stats each path through the caller's scoped store so vanished/revoked entries self-clean. Sidebar entry (clock icon) + files-style view, i18n ×4. Wired via the narrow `RecentsStore` interface to keep `internal/api` import-clean. Covered by `TestRecordDedupeCapOrder` / `TestPersistenceRoundTrip`.
Cordy closed this issue 2026-08-20 20:36:10 +00:00
Sign in to join this conversation.
No labels
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#293
No description provided.