Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions app/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ class Kernel extends HttpKernel
\App\Http\Middleware\ResolveConfigs::class,
\App\Http\Middleware\ResolveVerify::class,
\App\Http\Middleware\SetLocale::class,
\App\Http\Middleware\ClearBrowserCache::class,
'installation:complete',
'admin_user:set',
'accept_content_type:html',
Expand All @@ -86,7 +85,6 @@ class Kernel extends HttpKernel
\App\Http\Middleware\ResolveConfigs::class,
\App\Http\Middleware\ResolveVerify::class,
\App\Http\Middleware\SetLocale::class,
\App\Http\Middleware\ClearBrowserCache::class,
'accept_content_type:html',
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
Expand Down Expand Up @@ -115,7 +113,6 @@ class Kernel extends HttpKernel
\App\Http\Middleware\ResolveConfigs::class,
\App\Http\Middleware\ResolveVerify::class,
\App\Http\Middleware\SetLocale::class,
\App\Http\Middleware\ClearBrowserCache::class,
'accept_content_type:json',
'content_type:json',
\Illuminate\Cookie\Middleware\EncryptCookies::class,
Expand Down
34 changes: 0 additions & 34 deletions app/Http/Middleware/ClearBrowserCache.php

This file was deleted.

26 changes: 0 additions & 26 deletions app/Listeners/ClearBrowserCacheOnAuthChange.php

This file was deleted.

4 changes: 0 additions & 4 deletions app/Providers/EventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
use App\Events\TagAlbumSaved;
use App\Events\UserGroupMembershipChanged;
use App\Listeners\CacheListener;
use App\Listeners\ClearBrowserCacheOnAuthChange;
use App\Listeners\LogQueryTimeout;
use App\Listeners\ManagedCacheAlbumListingInvalidator;
use App\Listeners\ManagedCacheMapListingInvalidator;
Expand All @@ -59,7 +58,6 @@
use App\Listeners\RotateLicenseKeyOnLogin;
use App\Listeners\WebhookListener;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Auth\Events\Registered;
use Illuminate\Cache\Events\CacheHit;
use Illuminate\Cache\Events\CacheMissed;
Expand Down Expand Up @@ -158,8 +156,6 @@ public function boot(): void
Event::listen(PhotoPersonsChanged::class, RecomputeAlbumUserThumbsOnPhotoChange::class . '@handlePhotoPersonsChanged');

Event::listen(Login::class, RotateLicenseKeyOnLogin::class . '@handle');
Event::listen(Login::class, ClearBrowserCacheOnAuthChange::class . '@handle');
Event::listen(Logout::class, ClearBrowserCacheOnAuthChange::class . '@handle');

// Webhook dispatch for photo lifecycle events
Event::listen(PhotoAdded::class, WebhookListener::class . '@handlePhotoAdded');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,32 @@ Open questions for [Feature 102](spec.md). Log every high- and medium-impact que
| ~~Q-102-04~~ | 102 – Asset Endpoint Speedup | High | Should tiles get direct thumbnail URLs so they skip `/Asset`, and through which tier? | Resolved (Option A — viewport-scoped URL tier; FR-102-05, FR-102-06, ADR-102-01) | 2026-10-20 | 2026-10-21 |
| ~~Q-102-05~~ | 102 – Asset Endpoint Speedup | Medium | How does `/Asset` drop global middleware (SE verification)? | Resolved (Option A — route-group middleware; FR-102-07, ADR-102-02) | 2026-10-21 | 2026-10-21 |
| ~~Q-102-06~~ | 102 – Asset Endpoint Speedup | Medium | For an admin, does `/Asset` still answer 404 for an unknown album and 403 for a photo outside the album? | Resolved (Option A — admins skip both; FR-102-02) | 2026-10-21 | 2026-10-21 |
| ~~Q-102-08~~ | 102 – Asset Endpoint Speedup | High | How does a browser shared by several viewers avoid reusing assets privately cached for the previous one? | Resolved (Option A — `Clear-Site-Data: "cache"` on login/logout; FR-102-08) | 2026-10-21 | 2026-10-21 |
| ~~Q-102-08~~ | 102 – Asset Endpoint Speedup | High | How does a browser shared by several viewers avoid reusing assets privately cached for the previous one? | Resolved (front-end stores cleared on login/logout, Q-102-11 Option B; FR-102-08) | 2026-10-21 | 2026-10-29 |
| Q-102-07 | 102 – Asset Endpoint Speedup | Medium | Which temporary-link rule decides whether `/Photos/urls` gives an admin direct URLs? | Open | 2026-10-21 | 2026-10-21 |
| ~~Q-102-09~~ | 102 – Asset Endpoint Speedup | Medium | Should grid tiles get `small` instead of `small2x` on 1x screens? | Resolved (Option A — client sends its density; FR-102-05, FR-102-06) | 2026-10-21 | 2026-10-21 |
| ~~Q-102-10~~ | 102 – Asset Endpoint Speedup | Medium | How does the tile URL loader reach the grid and its tiles without `provide`/`inject`? | Resolved (Option C with the route detection of Option A; FR-102-06) | 2026-10-22 | 2026-10-22 |
| ~~Q-102-11~~ | 102 – Asset Endpoint Speedup | High | Without `Clear-Site-Data` (Chromium stalls login/logout), what keeps the next viewer of a browser from reusing `/Asset` files cached for the previous one? | Resolved (Option B — front-end stores cleared on login/logout; FR-102-08) | 2026-10-29 | 2026-10-29 |

## Question Details

### ~~Q-102-11~~ · Cached assets after login/logout without `Clear-Site-Data` ✅ RESOLVED

**Status:** Resolved — Option B. Encoded in spec FR-102-08.
**Feature:** F-102
**Priority:** High

`Clear-Site-Data: "cache"` makes Chromium scan its whole HTTP cache before completing the response: on a profile with a large cache, login and logout take 11–16 s ([discussion #4931](https://github.com/LycheeOrg/Lychee/discussions/4931), [Chromium issue 571157926](https://issues.chromium.org/issues/571157926)). The login and logout responses therefore do not send it; the front end clears its stores on login and logout.

The front end can clear the gallery stores (album, albums, photo, photos, tile URLs, timeline, search…), the object URLs of `ThumbAssetService` and the axios response cache. Logout reloads the page, which drops all of them; login keeps the page.

The front end cannot clear the browser HTTP cache. `/Asset` and `/Faces/{face_id}/photo/{variant}` answer `Cache-Control: private, max-age=3600` (FR-102-04), keyed by the URL alone. For an hour after a logout, a guest on the same browser who opens a photo they may see gets the previous viewer's file from the cache, without a request: the owner's unwatermarked thumbnail instead of the watermarked one. Private photos stay out of reach, since the guest's listings never name them.

- **Option A: clear the stores and key `/Asset` requests per session.** Front end only. A random key, regenerated on every login and logout and kept in `localStorage` so it survives the logout reload, is appended to `/Asset` and `/Faces/…/photo/…` requests (`?k=…`). Each session gets its own cache entries, so the HTTP cache never hands one viewer's file to the next, and the hour without requests stays. Costs a query parameter the server ignores. A login through OAuth (a server redirect, no front-end login action) keeps the guest's key until the next logout: that user may see the guest's watermarked thumbnails, never the reverse.
- **Option B (chosen): clear the stores only.** Exactly the front-end change, nothing more. For an hour after a logout, a guest on the same browser can get the owner's unwatermarked thumbnail of a photo the guest may see. After a login, the user sees the guest's watermarked thumbnails.
- **Option C: clear the stores and drop the hour without requests.** `/Asset` answers `private, no-cache`: every view asks the server and gets `304` while the file is unchanged (Q-102-03 Option B). Always correct, but it is a backend change and loses the requests FR-102-04 saves.

---

### ~~Q-102-10~~ · Handing the tile URL loader to the grid ✅ RESOLVED

**Status:** Resolved — Option C, with the source taken from the route as in Option A. The store keys URLs by photo id for the page's lifetime and empties only when the viewer changes. Encoded in spec FR-102-06.
Expand Down Expand Up @@ -65,7 +84,7 @@ Owner remark (2026-10-21): on a 1x screen a grid tile only needs `small`. Today

### ~~Q-102-08~~ · Privately cached assets on a shared browser ✅ RESOLVED

**Status:** Resolved — Option A. Encoded in spec FR-102-08.
**Status:** Resolved — front-end stores cleared on login and logout (Q-102-11 Option B). Encoded in spec FR-102-08.
**Feature:** F-102
**Priority:** High

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
_Linked specification:_ [spec.md](spec.md)
_Linked tasks:_ [tasks.md](tasks.md)
_Status:_ Draft
_Last updated:_ 2026-10-21
_Last updated:_ 2026-10-29

> Guardrail: Keep this plan traceable back to the governing spec. Reference FR/NFR/Scenario IDs from `spec.md` where relevant, log any new high- or medium-impact questions in the feature's [open-questions.md](open-questions.md), and assume clarifications are resolved only when the spec’s normative sections (requirements/NFR/behaviour/telemetry) and, where applicable, ADRs under `docs/specs/6-decisions/` have been updated.

Expand All @@ -16,7 +16,7 @@ A gallery page issues one image request per tile, so per-request cost multiplies

## Scope Alignment
- **In scope:** `GetPhotoAssetRequest` authorization reads; route-group middleware (ADR-102-02); cache headers in `ServesSizeVariantAssetTrait`; `GET /api/v3/Albums/{album_id}/Photos/urls`; v8 grid batching and the `Thumb` direct-`src` path.
- **Out of scope:** bootstrap cost; covers, Flow, map, search and face tiles (stay on `/Asset`); server-side caching of the URL tier; v2/v7.
- **Out of scope:** bootstrap cost; covers, Flow, map, search and face tiles (stay on `/Asset`); server-side caching of the URL tier; v2/v7, apart from the store reset on login and logout (FR-102-08).

## Dependencies & Interfaces
- `GetPhotoAssetRequest`, `ResolvesSizeVariantAssetTrait`, `ServesSizeVariantAssetTrait`, `AlbumFactory`, `AlbumPolicy` (Feature 056, ADR-0008); `FacePhotoAssetController` and its request (Feature 093, ADR-093-01).
Expand All @@ -35,7 +35,7 @@ A gallery page issues one image request per tile, so per-request cost multiplies
- Direct URL points at a missing or expired file (S3 presign) → `Thumb` falls back to `/Asset` on the image `error` event (FR-102-06).

## Implementation Drift Gate
After the last task: walk FR-102-01 … 07 and S-102-01 … 14 against tests and code, rerun the harness and record numbers below, confirm ADR-102-01/02 match the code, and append the drift report here.
After the last task: walk FR-102-01 … 08 and S-102-01 … 15 against tests and code, rerun the harness and record numbers below, confirm ADR-102-01/02 match the code, and append the drift report here.
Comment thread
ildyria marked this conversation as resolved.
Resolved

## Increment Map
1. **I1 – Lean `/Asset` authorization (FR-102-02)** — T-102-01, T-102-02.
Expand All @@ -61,17 +61,21 @@ After the last task: walk FR-102-01 … 07 and S-102-01 … 14 against tests and
- _Notes:_ `Thumb` takes `directUrl` (string → direct `src`, `null`/absent → `/Asset`) and `pending` (placeholder only), so covers, search and other callers keep today's behaviour without passing anything. Tiles read `useTileUrlsStore().urlsOf()` themselves rather than receiving URLs through `PhotoGridVirtual`. `useTileUrlRequests` keys on the rendered ids and on the store's `generation`, which a source or viewer change bumps, so an unchanged id set is asked again.
7. **I7 – Verification and docs (NFR-102-01)** — T-102-19.
- _Steps:_ Browser check on the scratch instance (album, timeline, smart album; unprotected and temporary-link instance; network panel). Harness rerun. Knowledge map, roadmap, Feature 056 spec cross-references to FR-102-02/04.
8. **I8 – Per-viewer cache safety (FR-102-04, FR-102-08)** — T-102-20, T-102-21.
- _Steps:_ Tests first (`ETag` on 200, matching `If-None-Match` → 304, watermark switched on under the same URL → 200 with the watermarked file; `Clear-Site-Data: "cache"` on login and logout only). `ETag` of path, mtime and size in `ServesSizeVariantAssetTrait::responseFile()`; `ClearBrowserCacheOnAuthChange` listener on `Login`/`Logout` sets a request flag; `ClearBrowserCache` middleware in `web`, `web-admin`, `api` adds the header.
- _Commands:_ `php artisan test --filter='PhotoAssetV3Test|AssetMiddlewareV3Test|FacePhotoAssetTest'`, `php artisan test --filter='AuthTest|LdapAuthTest|EmailLoginTest|WebAuthn'`, `make phpstan`.
- _Notes:_ Prompted by the PR #4912 review (cache reuse across sessions, `304` for another selected file). The v8 in-memory thumbnail cache (`thumb-asset-service.ts`) is dropped by the full page load that follows logout.
8. **I8 – `ETag` on `/Asset` (FR-102-04)** — T-102-20, T-102-21.
- _Steps:_ Tests first (`ETag` on 200, matching `If-None-Match` → 304, watermark switched on under the same URL → 200 with the watermarked file). `ETag` of path, mtime and size in `ServesSizeVariantAssetTrait::responseFile()`.
- _Commands:_ `php artisan test --filter='PhotoAssetV3Test|AssetMiddlewareV3Test|FacePhotoAssetTest'`, `make phpstan`.
- _Notes:_ Prompted by the PR #4912 review (`304` for another selected file).
9. **I9 – Grid tile variant by density (FR-102-05, FR-102-06)** — T-102-22 … T-102-24.
- _Steps:_ Tests first (`density=1|2` chains, default 2, 422 otherwise; loader query and tile fallback type). `GetPhotoUrlsRequest` validates `density`; `QueryPhotoUrls` picks the chain; `PhotoUrlResource.grid_urls`; regenerate types; loader and `PhotoThumbVirtual`.
- _Commands:_ `php artisan test --filter=PhotoUrlsV3Test`, `make phpstan`, `vp test`, `vp check --fix`, `vp run typecheck`.
- _Notes:_ Owner remark 2026-10-21 (Q-102-09): 1x screens downloaded `small2x` on both tiers.
10. **I10 – Tile URL store driven by the route (FR-102-06)** — T-102-25 … T-102-27.
- _Steps:_ Vitest first for `tileUrlSource()` (route → album id, `timeline` or none) and for the `TileUrlsState` store (batching, per-viewer map kept across sources, missed ids per source, all-null disables per source, viewer change empties it). `useTileUrlRequests()` in `PhotoGridVirtual` sets the source from `useRoute()` and the viewer from `UserState`; `PhotoThumbVirtual` and `PhotoListItemVirtual` read the store; `AlbumPanel.vue` and `Timeline.vue` no longer build a loader.
- _Notes:_ No `provide`/`inject` for tile URLs (Q-102-10). URLs are keyed by photo id because a photo's file URL is the same in every album for one viewer; a URL gone stale (rotation, regenerated variant, expired S3 link) fails to load and `Thumb` falls back to `/Asset`. `MOTION_URL_LOADER` (Feature 100) keeps its injection.
11. **I11 – Front-end store reset on login and logout (FR-102-08)** — T-102-29 … T-102-32.
- _Steps:_ Vitest first for `ThumbAssetService.clear()` (fetches again, revokes the object URLs nothing renders, revokes a rendered one at its last release) and `useClearViewerState()` (stores, timeline init data, dates and struct-of-arrays state, user, album list, axios cache, `ThumbAssetService`). Then the shared composable `resources/js/composables/useClearViewerState.ts`, called by `useLogout()` (v8), the v7 left-menu logout and the v7/v8 `LoginForm` and `WebauthnModal`. Remove `ClearBrowserCache`, `ClearBrowserCacheOnAuthChange` and their `AuthTest` cases (Q-102-11).
- _Commands:_ `vp test resources/js/services resources/js/composables`, `vp check` (changed files), `vp run typecheck`, `php artisan test --filter='Tests\\Feature_v2\\AuthTest'`, `vendor/bin/php-cs-fixer fix`, `make phpstan`.
- _Notes:_ Login opens only while no photo is shown, and every `logged-in` handler reloads its view's data (`refresh()`, `onLoggedIn()`, `handleLoggedIn()`, `goBack()`), so the stores are cleared before it. Emptying the album and albums stores also unmounts the rendered tiles, so the reloaded view asks `ThumbAssetService` again. `TimelineState.reset()` clears the init data (`load()` returns early once `layout` is set, so the rights and config would outlive the viewer), the v2 `dates` (`loadDates()` returns early once filled) and calls `resetV3()` (`ensureBucketsV3()` returns early once `bucketsV3` is set, so would the tiles); `initialLoad()` rewrites the v2 pages. The Timeline and Tag views mount their login modals while the user is not logged in (`!userStore.isLoggedIn`, as Album and Albums do): `clearViewerState()` empties the user, so a guard on `isGuest` would unmount the modal before it emits `logged-in` and the view would never refresh.

## Scenario Tracking

Expand All @@ -83,7 +87,8 @@ After the last task: walk FR-102-01 … 07 and S-102-01 … 14 against tests and
| S-102-08 … 12 | I4 / T-102-09 … T-102-12 | `PhotoUrlsV3Test` |
| S-102-13 | I5, I10 / T-102-13, T-102-14, T-102-25 … T-102-27 | tile URL store Vitest |
| S-102-14 | I6 / T-102-15 … T-102-18, I7 / T-102-19 | composable + `Thumb` Vitest, browser check |
| S-102-06, S-102-07, S-102-15 | I8 / T-102-20, T-102-21 | `PhotoAssetV3Test`, `AuthTest` |
| S-102-06, S-102-07 | I8 / T-102-20, T-102-21 | `PhotoAssetV3Test` |
| S-102-15 | I11 / T-102-29 … T-102-32 | `thumb-asset-service.test.ts`, `useClearViewerState.test.ts` |

## Analysis Gate
2026-10-21, agent self-review requested by the owner ("check for consistencies, ambiguities").
Expand Down Expand Up @@ -137,6 +142,8 @@ Browser check (I7, 2026-10-21, scratch instance; uploads served same-origin-allo

Found: with `when_logged_in=0` and `when_admin=1`, an admin still gets direct URLs (`shouldNotUseSignedUrl()`), while `/Asset` requires the admin's signature → Q-102-07.

Browser check (I11, 2026-10-29, scratch instance, root gallery in v8 and v7): login and logout answer `204` without `Clear-Site-Data`; after login the root listings reload as the admin; logout lands on `/gallery`; no console error apart from the security advisory check, which needs the network.
Comment thread
ildyria marked this conversation as resolved.
Resolved

## Exit Criteria
- Full quality gate green (php-cs-fixer, `vp check --fix`, `vp run typecheck`, scoped tests, `make phpstan`); `php artisan typescript:transform` rerun.
- Harness numbers recorded here; roadmap and knowledge map updated; ADR-102-01/02 match the code; drift gate report appended.
Expand Down
Loading
Loading