release: v0.13.0 (#535)

Twenty-nine commits since v0.12.0. See CHANGELOG.md for the full list
and the upgrade notes.

Ten additions — the SEO package (site URL, robots.txt, sitemap.xml,
JSON-LD), slideshow mode, opt-in original downloads, copy-link,
previous/next album navigation, alt text from the Immich description,
per-album location precision, the update notice, a Help tab, and a
subpage cover grid separate from its photo grid. Two fixes: CJK album
slugs, and the lightbox control bar.

Photo of the Day (#476) was built on `dev` and reverted as not planned;
it never appears in a release.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01DmsHVRL9yQvckUB1ZpVWre

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Ralksta
2026-08-25 10:08:12 +02:00
committed by GitHub
parent 10ec68f493
commit a813433891
73 changed files with 4697 additions and 453 deletions

View File

@@ -17,6 +17,7 @@ import {
assetPlaceholder,
assetExifSummary,
assetAspectRatio,
assetCaption,
} from '@/lib/urls';
describe('imageUrl', () => {
@@ -168,3 +169,58 @@ describe('assetAspectRatio', () => {
).toBeUndefined();
});
});
describe('assetCaption', () => {
/** An asset carrying nothing but the description under test. */
const withDescription = (description: string | null) => ({
exifInfo: {
make: null,
model: null,
lensModel: null,
focalLength: null,
fNumber: null,
exposureTime: null,
iso: null,
exifImageWidth: null,
exifImageHeight: null,
latitude: null,
longitude: null,
city: null,
state: null,
country: null,
dateTimeOriginal: null,
description,
},
});
it('returns the description when captions are shown', () => {
expect(assetCaption(withDescription('A gull over the harbour'), true)).toBe(
'A gull over the harbour',
);
});
it('trims surrounding whitespace', () => {
expect(assetCaption(withDescription(' Low tide\n'), true)).toBe('Low tide');
});
it.each([
['null', null],
['empty', ''],
['whitespace only', ' \n\t '],
])('returns undefined for a %s description, leaving alt=""', (_label, value) => {
expect(assetCaption(withDescription(value), true)).toBeUndefined();
});
it('returns undefined when the asset has no exifInfo at all', () => {
expect(assetCaption({ exifInfo: undefined }, true)).toBeUndefined();
});
/**
* The point of #506: the description is the one field that can hold private
* notes. Alt text publishes it as surely as a visible caption does, so the
* caption switch has to gate it too.
*/
it('withholds the description when captions are switched off', () => {
expect(assetCaption(withDescription('Client hates this crop'), false)).toBeUndefined();
});
});