release: v0.19.0

Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of
[CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md)
for the full list.

Highlights: client proofing links (#683), custom content pages (#738),
asset picker search (#740), concurrent-edit detection (#742), env-locked
settings (#739), an accessibility baseline (#741), three security fixes
with advisories (#737, #755, #759), and a pre-release quality pass
(#780–#797).

---------

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>
Co-authored-by: Ralf <rohlenbo79@gmail.com>
Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com>
Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
This commit is contained in:
Ralksta
2026-09-29 22:50:30 +02:00
committed by GitHub
parent 5f251923f0
commit ad8b09700b
297 changed files with 19398 additions and 1800 deletions

View File

@@ -1,7 +1,7 @@
// @vitest-environment jsdom
import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
import { ConfirmProvider, useConfirm } from '../components/ConfirmDialog';
import { ConfirmProvider, useChoice, useConfirm } from '../components/ConfirmDialog';
afterEach(cleanup);
@@ -60,3 +60,46 @@ describe('ConfirmDialog', () => {
await waitFor(() => expect(results).toEqual([false, false]));
});
});
/** The concurrent-edit prompt (#601): more than two ways out. */
function ChoiceTrigger({ onResult }: { onResult: (id: string | null) => void }) {
const choose = useChoice();
return (
<button
onClick={async () =>
onResult(
await choose({
title: 'Changed elsewhere',
actions: [
{ id: 'overwrite', label: 'Overwrite anyway' },
{ id: 'reload', label: 'Reload (discard mine)' },
],
cancelLabel: 'Keep editing',
}),
)
}
>
choose
</button>
);
}
describe('useChoice', () => {
it('resolves to the picked action, or null on cancel', async () => {
const results: (string | null)[] = [];
render(
<ConfirmProvider>
<ChoiceTrigger onResult={(id) => results.push(id)} />
</ConfirmProvider>,
);
fireEvent.click(screen.getByText('choose'));
fireEvent.click(screen.getByRole('button', { name: 'Reload (discard mine)' }));
await waitFor(() => expect(results).toEqual(['reload']));
fireEvent.click(screen.getByText('choose'));
fireEvent.click(screen.getByRole('button', { name: 'Overwrite anyway' }));
await waitFor(() => expect(results).toEqual(['reload', 'overwrite']));
fireEvent.click(screen.getByText('choose'));
fireEvent.click(screen.getByRole('button', { name: 'Keep editing' }));
await waitFor(() => expect(results).toEqual(['reload', 'overwrite', null]));
});
});