Update in-memory rate limiter to fail secure (reject new requests) when maximum capacity is reached, rather than evicting the oldest tracking entry. This prevents an attacker from bypassing their rate limit by flooding the store with spoofed `x-forwarded-for` IPs to intentionally trigger cache eviction. Also cleaned up temporary files and updated tests to verify the behavior.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Destructures `remaining` and `resetAt` from `checkRateLimit` and adds standard rate-limiting headers (`Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`) to the 429 Too Many Requests response.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Upgraded `encodeAssetId` and `decodeAssetId` to use `aes-256-gcm` (Authenticated Encryption) instead of `aes-256-cbc`.
AES-CBC lacks authentication, leaving it potentially susceptible to padding oracle attacks or ciphertext manipulation.
The new token adds a `v2:` prefix which strictly enforces AES-GCM while supporting legacy fallback for cached urls.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Added comprehensive `aria-label` to the parent `<Link>` in `SubpageGridView`.
- Hid redundant textual child elements using `aria-hidden="true"`.
- Removed redundant `alt` text from decorative thumbnail `Image`.
- Removed unused `albumMap` prop in `AlbumGrid`.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Implement an in-memory `Map` to cache parsed YAML objects.
- Use `structuredClone` to prevent caller mutations from corrupting the cached object state.
- Implement negative caching for missing files.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Avoids unnecessary O(N) memory allocation and copy by returning `Buffer.from(...)` directly since Buffer inherits from Uint8Array in Node.js.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Add Promise deduplication (request coalescing) to the `getAssetInfo`
method in `ImmichClient` (`lib/immich.ts`) to prevent concurrent
identical requests from firing multiple times to the Immich server.
This is particularly helpful when calling `Promise.all` on multiple
assets concurrently (e.g. hero images on the homepage or multiple
album cover placeholder hashes).
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* Added aria-hidden="true" to decorative SVG icons within interactive elements (buttons, links) in Lightbox, ThemeToggle, Footer, BackLink, and MapView.
* Added role="alert" and aria-live="polite" to the MapView loading indicator.
* Documented learning in .Jules/palette.md
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Adds an `aria-label` to the `BackLink` component to improve screen reader accessibility. By default, it was an icon next to text that functioned as a single link, but providing an explicit `aria-label` makes its target clearer.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Adds `role="dialog"`, `aria-modal="true"`, and `aria-label` to the `.overlay` container to improve screen reader accessibility. Also adds `aria-expanded` and `aria-controls` to the EXIF info toggle button, linking it to the newly ID'd `#exif-panel`.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Added aria-labels to the password input in `components/PasswordGate.tsx` and the grid columns/gap inputs in `components/DevToolbar.tsx` to provide clear semantic meaning for screen readers.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Prevents spurious 429s during normal browsing/refreshing.
Browser now caches personalised map data for 5 minutes (private,
so CDNs/proxies won't share it between users).
- escapeHtml() in MapView.tsx sanitises city/country/url/name before HTML injection
- HeroTextContent extracted to top-level component with explicit props (was nested inside async Server Component)
- rate-limit.test.ts: add evictIfFull stress test (6/6 pass)
ssr: false is not allowed with next/dynamic in Server Components.
Extracted the dynamic() call into DevToolbarLoader.tsx ('use client')
so layout.tsx stays a pure Server Component.
- Remove dead theme preset (no CSS, not in DevToolbar)
- Extract from closure → module-level export in lib/config/index.ts
- Extract helper in app/[...path]/page.tsx to deduplicate password gate check
- Extract inner component in app/page.tsx (was 4× inline in minimal/fullbleed/mosaic/split)
- Add 8 unit tests for buildSubpageGrid covering undefined, subset fields, layout fallback, and all fields
- Override inherited 2-column grid from .hero by adding display: block to .hero--mosaic
- Add explicit grid-column/grid-row for mosaic cells 3 & 4 to prevent auto-placement issues
- Slice heroData to 4 images to match 4 defined grid cells
- Regenerate hero-mosaic-home.png and theme-studio-home.png screenshots
The request.ip property no longer exists on NextRequest in Next.js 15+.
Fall back to x-real-ip (set by trusted reverse proxies) then x-forwarded-for.
The security intent from PR #4 is preserved — x-real-ip is preferred over
the more easily spoofed x-forwarded-for header.
Fixes build error: Property 'ip' does not exist on type 'NextRequest'