Repository navigation
Conversation
Closes #551. ## The bug `findUnwritable()` asks whether *this process* can write; `checkWritable()` phrased the answer as "Saving from the admin panel will fail, and so will backups." In the panel that is exactly right — it runs inside the app, as the app's user. In a CLI it is a different question, and run from the host against a container that writes as uid 1001, it produced this on a deployment with nothing wrong with it: ``` ERRORS (1) x 1 content path is not writable Saving from the admin panel will fail, and so will backups. Check the ownership of the mounted volume: content/.backups ``` The worst part is the advice: chowning the mounted volume from the host is the one change that would actually break the container's access. ## After ``` NOTES (1) i 1 content path is not writable by uid 1000 — but it is owned by uid 1001 Checked as the user running this command, not as the user the app runs as. The paths belong to uid 1001, which can write them; the Docker image runs the app as uid 1001. This only matters if the app itself runs as uid 1000. To check as the app: docker compose exec folio npm run doctor. Paths: content/.backups 0 errors, 0 warnings, 0 checks passed, 1 note. ``` `findUnwritable()` returns each path's owner, and the level follows what the answer is worth: | Situation | Level | | --- | --- | | everything writable | `ok`, a normal passed check | | unwritable, CLI runs as the owner | `error`, wording unchanged — this one really is about the app | | unwritable, owned by someone else | note: both uids named, `docker compose exec` suggested | | no `getuid()` (Windows) | note: claims nothing, and never prints `undefined` | The `id` stays `content-writable`, so the panel and the CLI still name the same check. ## One design change from #550 Notes move from an id set to a `note` flag on the finding. `content-writable` is a note in one case and a real error in the other, and an id cannot express that — the id set would also have swallowed a healthy `content/ is writable` into NOTES. The flag sits on a CLI-local `CliFinding = DoctorFinding & { note?: boolean }` rather than on `DoctorFinding`, which is shared with the panel where all of these checks are real. A test pins that the flag, not the id, is what decides. ## Verification - `npx tsc --noEmit` — 0 errors - `npm run test:unit` — 863 passed, incl. 9 new (owner recorded by `findUnwritable`, each of the four `reportWritable` outcomes, mixed ownership, the stable id, and the flag-not-id rule) - `npm run build` — passes - `npm run lint` — unchanged from the baseline on `dev` - Ran end to end: the error branch against a staged read-only directory I own (`EXIT=2`, wording unchanged), the note branch rendered through `formatReport`, and the healthy path against a real `content/` `docs/deployment.md` now describes both reported-not-judged findings together. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
The `[Unreleased]` section already carried the four bug fixes and the smoke test. This adds the two entries that were missing and tidies the structure, so the release commit only has to rename the heading. ## Added - **`npm run doctor`** ([#521](#521)) — what it checks, that it runs inside the shipped image, the exit-code mapping, and the two findings that are reported rather than judged. - The admin smoke test entry was already there. ## Changed - **The form-field layer** ([#533](#533)) — framed as what the panel costs to change rather than as a file listing, with the two things deliberately left undone named so nobody has to re-derive them from the issue. ## Structure The section had grown **two `### Fixed` headings**. Now one each of Added / Changed / Fixed, in Keep a Changelog's order. No existing entry was reworded — the diff moves them, and a before/after comparison of the entry list confirms only the two new ones were added and none were lost. ## What is deliberately absent - **[#542](#542 (THEME_INFO used before initialization) — a regression of the field layer, fixed before either shipped. In v0.13.0 the declaration sat at line 108 and its use at 1034; `a3c6611` moved the use to 108 and left the declaration at 370. No released version has this bug, so it is not release notes. It is mentioned where it belongs: as the reason the admin smoke test exists. - **[#551](#551 (writability reported for the wrong user) — a fix to the doctor CLI that this same release introduces. The corrected behaviour is described as how the doctor works, not as a fix to something users had. Verified with `npx prettier --check`. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Closes the Unreleased section. Thirteen commits since v0.13.0: the config doctor moved to the terminal, the admin panel got a form-field layer under an unchanged surface, and a Playwright spec now loads all fourteen admin routes in a real browser. The documentation screenshots are still the v0.12.0 set, for the same reason as last time — the panel renders client-side against a live Immich instance, so `docs/screenshots/admin-*.png` cannot be recaptured from a bare checkout. They have now been stale for two releases.
CodeQL flagged the admin smoke-test job added in #545 for running with the default GITHUB_TOKEN permissions. The older job had the same gap and was never flagged — an alert is raised for changed code, and that job predates the scan. Neither job writes anything back: they check out the code, build it and report through the run. One top-level `permissions: contents: read` covers both, so a third job cannot inherit a writable token by being added without one.
main is a chain of one squash commit per release, each tree-identical to the dev commit it was made from. Because a squash leaves no parent link, the merge base of dev and main stays at the previous release, so every release PR re-presents the whole of the last release as a conflict: CHANGELOG.md, package.json, package-lock.json, admin.css and PageBuilder.tsx all changed on both sides. This merge takes dev's tree unchanged, which loses nothing: main's tree already equals that of an ancestor of dev, and the diff from main to dev is byte-identical to the diff from that ancestor. What it does is move the merge base to main, so this release PR and the ones after it apply cleanly.
…ched (#558) ## Was das behebt `/api/image/:token?size=original` hat die unveränderte Originaldatei aus Immich durchgereicht. Das Asset-Token autorisiert aber **das Bild, nicht die Größe** — ein aus dem öffentlichen HTML gelesenes Thumbnail-Token plus `?size=original` lieferte damit das Original samt GPS-Koordinaten an jeden nicht angemeldeten Besucher. An der Live-Galerie nachgemessen: | Fassung | Größe | EXIF | |---|---|---| | preview | 176 KB | kein EXIF-Block | | original | 16 MB | GPSInfo-Tag + Kameramodell | Das ging an zwei bewussten Schutzmaßnahmen vorbei: `app/api/exif/[id]` gibt dem Browser nur `city`/`country`, und `/api/map` quantisiert Koordinaten serverseitig. ## Warum genullt und nicht herausgeschnitten Die Bilddaten bleiben bitgenau erhalten — **Neu-Encodieren kostet Qualität und kam deshalb nicht infrage**. Nur die Metadaten-Bytes werden mit Nullen überschrieben. Das hält außerdem die Dateilänge konstant, womit `Content-Length` gültig bleibt und — bei AVIF entscheidend — die absoluten Offsets in der `iloc`-Box weiter stimmen. Ein Item zu entfernen hieße, sämtliche Offsets im Container neu zu berechnen. Ein genulltes APPn-Segment ist für Decoder schlicht ein unbekanntes Anwendungssegment und wird übersprungen. ## Umfang **Entfernt:** JPEG APP1 (EXIF/XMP), APP13 (IPTC), COM · `Exif`- und `mime`-Items in ISOBMFF-Containern (AVIF/HEIC/HEIF), gefunden über `iinf`/`iloc`. **Bewusst behalten:** JPEG APP0 (JFIF) und APP2 (ICC). Das Farbprofil zu verwerfen würde die Farben sichtbar verschieben — das wäre eine Darstellungs-, keine Datenschutzänderung. **Nicht angefasst:** die Download-Route pro Album. Die ist sauber abgesichert (Allowlist, `download: true`, Passwort-Gates, Asset-Zugehörigkeit) und eine bewusste Opt-in-Funktion. Ein Format, das wir nicht sicher parsen können, fällt jetzt auf die Vorschau zurück, statt durchgereicht zu werden — genau dieses stillschweigende Durchreichen war die Ursache. ## Tests 8 neue Unit-Tests in `lib/__tests__/metadataStrip.test.ts` gegen synthetisch gebaute JPEG- und AVIF-Strukturen: GPS verschwindet, ICC und JFIF bleiben, Bilddaten und Dateilänge sind Byte für Byte unverändert, kaputte Container liefern `null`. Gesamte Suite: 871 Tests grün, `tsc` und `eslint` sauber, `next build` läuft durch. ##⚠️ Beim Ausrollen zu beachten Diese URLs werden als `immutable` mit einem Jahr Cache ausgeliefert. Bereits ausgelieferte Originale bleiben in Browser-Caches also erhalten, auch nach dem Deploy. **`IMAGE_CACHE_VERSION` muss hochgezählt werden**, weil das die URLs selbst ändert; das eingebaute `STRIP_VERSION` deckt nur Caches ab, die revalidieren. Ob ein bereits abgeflossenes Original bei Dritten liegt, lässt sich naturgemäß nicht rückgängig machen. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01BkUJtcTjmKHPoUwxArAdGb Co-authored-by: Ralf <rohlenbo79@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
assetAspectRatio() used the stored sensor dimensions as-is. Cameras that record a portrait as landscape pixels plus orientation 6 or 8 got a landscape tile for every portrait, which flattened masonry into a uniform grid. Orientations 5-8 now swap width and height. Closes #565
…sory (#571) - Raise --text-muted to 4.5:1 in every theme; move --text-secondary along in minimal and monograph. - Give the admin panel a visible focus ring (WCAG 1.4.11) and remove a permanent outline: none. - Replace nine undefined tokens that made the proofing modal and journal cards unreadable in the light theme. - Add useModalDialog: Escape, focus in, focus trap and focus return for four dialogs. - Give eight admin select elements an accessible name. - Bump next and eslint-config-next to 16.3.4 (GHSA-p293-qw3h-jr36) and js-yaml to 4.3.2.
## Why #572 by @ImScheinox fixed the masonry EXIF-orientation bug (#565) on 11 September. It was overlooked, and #576 fixed the same bug again on 19 September. The fix should land under the person who found it first. ## What This PR reverts 9a87895 (#576). On its own it reintroduces the bug, so **merge #572 immediately afterwards**. #572 conflicts with `dev` today (both touch `lib/urls.ts` and `lib/immich.ts`). Once this revert is in, it merges cleanly. ## The two fixes compared Both swap width and height for orientations 5–8 and are tested against the same Sony 7008×4672 frame. The only difference: #572 parses the flag with `Number()`, so it also works if Immich ever sends the orientation as a number rather than a string. #576 matches strings only. ## Verified On `dev` + this revert + #572 merged locally: - `npx vitest run lib/__tests__/urls.test.ts`: 29/29 pass - `npx tsc --noEmit`: 0 errors Refs #565, #572 Co-authored-by: ralksta <ralksta@users.noreply.github.com>
## Summary
`assetAspectRatio()` computes the ratio from `exifImageWidth /
exifImageHeight` without consulting the EXIF `orientation` flag. Most
cameras read the sensor out in landscape however the body is held and
record the quarter turn as a flag rather than rewriting the pixels, so a
portrait frame arrives as e.g. `7008x4672` with `orientation: "6"`.
The browser rotates the image correctly on display, but the layout has
already reserved a landscape tile. In `layout: masonry` with
`aspectRatio: auto`, every photo from such a camera is treated as
landscape and the grid loses its staggered look entirely.
First five assets of an affected album, via `/search/metadata` with
`withExif: true` — four landscape frames and one portrait frame,
identical stored dimensions, differing only in the flag:
```json
[{"f":"DSC00308.JPG","w":7008,"h":4672,"o":"1"},
{"f":"DSC00307.JPG","w":7008,"h":4672,"o":"1"},
{"f":"DSC00302.JPG","w":7008,"h":4672,"o":"1"},
{"f":"DSC00295.JPG","w":7008,"h":4672,"o":"1"},
{"f":"DSC01158.JPG","w":7008,"h":4672,"o":"6"}]
```
Immich's own web UI reports `DSC01158` as 4672 × 7008 because it applies
the flag. The API does not.
**Change:**
- `lib/urls.ts` — `assetAspectRatio()` swaps width and height for
orientations 5–8. Values 1–4 are upright or mirrored in place and leave
the ratio untouched. The flag arrives as a string, so it is parsed with
`Number()`; anything unparseable falls through as upright, which is the
previous behaviour.
- `lib/immich.ts` — `orientation` added to `ImmichExifInfo` as an
optional field. Immich only fills it when the file carries the tag, and
making it required would break every existing EXIF fixture.
- `lib/__tests__/urls.test.ts` — cases for all eight orientations plus
null and absent.
`assetAspectRatio()` is the single place dimensions become a ratio, so
this covers masonry, justified and editorial-flow at once. The uniform
grid is unaffected, as it does not use the natural ratio.
Verified against a live album of Sony ILCE-7M5 JPEGs mixing both
orientations: portrait frames now render as taller tiles.
## Type of change
- [x] 🐛 Bug fix
- [ ] ✨ New feature
- [ ] 🎨 Style / visual
- [ ] ♻️ Refactor
- [ ] 🔒 Security fix
- [ ] 📄 Documentation
- [ ] 🛠️ Build / CI
## Checklist
- [x] Targets the `dev` branch (see
[CONTRIBUTING.md](../CONTRIBUTING.md#branches))
- [x] `npm run build` passes locally
- [x] `npx tsc --noEmit` passes (0 type errors)
- [x] `npx vitest run` passes (all tests green)
- [x] No secrets / API keys in diff
- [ ] Documentation updated if needed (`docs/`, `README.md`)
- [x] Screenshots attached if there are visual changes
## Screenshots
<img width="1216" height="735" alt="image"
src="https://github.com/user-attachments/assets/6f823ef7-1d70-48ee-98c6-a5139a694b73"
/>
<img width="1172" height="837" alt="image"
src="https://github.com/user-attachments/assets/76eb2f32-50d8-48ba-af4c-d99076612b21"
/>
## Summary Enables Markdown links in journal/essay **quote attributions**, per #559. The quote body and photo captions already go through `renderInlineMarkdown`, so `[text](url)` becomes an anchor there — but the attribution was the one inline field rendered as plain text, so a link in `Quote text -- [Source](https://…)` showed as literal markdown. ## What changed `app/[...path]/EssayView.tsx` — render the attribution the same way as the quote body: ```tsx <span className="essay-quote__author" dangerouslySetInnerHTML={{ __html: `— ${renderInlineMarkdown(block.author)}` }} /> ``` The attribution keeps its Markdown in the parsed block and is rendered at display time, which means it works: - in the published journal page and photo essays (they share `EssayView`), - in the Journal Studio live preview (which renders the same component), - from both the raw-Markdown file *and* the block editor — because the field stays Markdown, typing `[link](…)` in the author input round-trips untouched and renders live. ## Security No new surface: it reuses `renderInlineMarkdown`, which escapes everything first and then emits only bold/italic/anchors, with the URL scheme allowlisted (`http/https/mailto/tel`/relative/anchor). `javascript:`/`data:` URLs render as plain text, exactly as captions behave today. ## Tests `lib/__tests__/essay.test.ts` — three cases: - a link in the attribution is preserved for rendering, - it survives a serialize → parse round-trip, - an unsafe scheme (`javascript:`) is not turned into an anchor. ## Notes - **Photo captions already support links** — they've always run through `renderInlineMarkdown` (this also answers the question raised in #559). - Out of scope, flagged for later: links in paragraphs/captions still get *dropped* on a save through the block editor (the serializer undoes `<strong>`/`<em>` but strips `<a>`). The attribution is unaffected because it stays Markdown. Fixing that would be a small follow-up in `serializeJournalMarkdown`.
…560) ## Problem With page transitions enabled (the default — `settings.yaml` → `transitions`), the client proofing UI never floats. The "❤️ N Selected" bar and its **Share & Export** modal are `position: fixed`, but they rendered at the bottom of the page content instead of over the viewport — scroll up and they scroll away with the page. ## Root cause `app/globals.css` fades the page in with a transform: ```css [data-transitions='true'] .main { animation: page-fade-in 0.5s ease-out forwards; } @Keyframes page-fade-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } ``` Per the CSS spec, an element with a `transform` establishes a **containing block** for its `position: fixed` descendants. Two things compound this: 1. `animation-fill-mode: forwards` keeps the `to` keyframe applied **forever** after the 0.5s animation finishes, so `.main` never loses its transform. 2. Even the *identity* `translateY(0)` counts — any value other than `none` creates the containing block. The result: the proofing bar (`.proofing-sticky-bar`) and the modal overlay (`.proofing-modal-overlay`) resolve their `fixed` coordinates against `.main` instead of the viewport, so they sit at the bottom of the page content. This also silently breaks any other `position: fixed` element rendered inside `<main>` — present or future. ## Fix Drop the transform and keep only the opacity fade: ```css @Keyframes page-fade-in { from { opacity: 0; } to { opacity: 1; } } ``` `opacity` is safe: it creates a stacking context but **not** a containing block, so `position: fixed` keeps working. The visual change is the loss of the 8px slide; the fade — which is the dominant effect — is unchanged. ## Scope - `app/globals.css` — one keyframe, plus a comment explaining *why* the transform must not come back. ## Verification - `npm run build` ✅ - `npx tsc --noEmit` ✅ - `npx vitest run` ✅ (no test exercises CSS animation; this is a rendering fix) ## Notes for review - This is deliberately a standalone fix, separate from the download feature it was discovered alongside: the proofing bar/modal predate the download work and were already broken. - The fix is behavioural (removes an 8px slide) but not a breaking change — no config, markup, or API is touched.
…562) ## Problem The client-proofing selection UI — the floating "❤️ N Selected" bar and the **Share & Export** modal — ignores the page's colour mode. In light mode the text turns dark but the bar/modal stay dark, so the text is unreadable. ## Root cause The bar and modal were styled with two CSS custom properties that no theme defines: - `var(--bg-surface, #1e1e1e)` — `--bg-surface` does not exist in `app/tokens.css`, so it **always** falls back to the hardcoded dark `#1e1e1e`. - `var(--border-color, rgba(255,255,255,0.15))` — `--border-color` also does not exist, so the border stays a white-alpha value that only reads correctly on dark. - The secondary buttons used hardcoded `rgba(255,255,255,…)` backgrounds/borders. `--text-primary` *is* a real, themed token, which is why the text switched to dark while the surfaces stayed dark — the mismatch made it unreadable. ## Fix Swap the undefined tokens for the ones the rest of the site already uses (all defined for both dark and light in every preset): | Before | After | | --- | --- | | `var(--bg-surface, #1e1e1e)` | `var(--bg-card)` | | `var(--border-color, …)` | `var(--border-subtle)` | | `rgba(255,255,255,0.1/0.15)` button surfaces/borders | `var(--bg-card-hover)` / `var(--border-subtle)` | Also, the "❤️ N Selected" filter chip now uses `var(--text-primary)` when inactive instead of hardcoded white, so it reads on a light surface. ## Scope - `components/ProofingModal.tsx` - `app/[...path]/PhotoGrid.tsx` - `app/[...path]/EssayView.tsx` (essays render a duplicate of the selection bar) ## Verification - `npx tsc --noEmit` ✅ - `npm run build` ✅ - `npx vitest run` ✅ No config, markup, or API changes — this is purely swapping colour tokens. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/actions/upload-artifact/releases">actions/upload-artifact's releases</a>.</em></p> <blockquote> <h2>v7.0.0</h2> <h2>v7 What's new</h2> <h3>Direct Uploads</h3> <p>Adds support for uploading single files directly (unzipped). Callers can set the new <code>archive</code> parameter to <code>false</code> to skip zipping the file during upload. Right now, we only support single files. The action will fail if the glob passed resolves to multiple files. The <code>name</code> parameter is also ignored with this setting. Instead, the name of the artifact will be the name of the uploaded file.</p> <h3>ESM</h3> <p>To support new versions of the <code>@actions/*</code> packages, we've upgraded the package to ESM.</p> <h2>What's Changed</h2> <ul> <li>Add proxy integration test by <a href="https://github.com/Link"><code>@Link</code></a>- in <a href="https://redirect.github.com/actions/upload-artifact/pull/754">actions/upload-artifact#754</a></li> <li>Upgrade the module to ESM and bump dependencies by <a href="https://github.com/danwkennedy"><code>@danwkennedy</code></a> in <a href="https://redirect.github.com/actions/upload-artifact/pull/762">actions/upload-artifact#762</a></li> <li>Support direct file uploads by <a href="https://github.com/danwkennedy"><code>@danwkennedy</code></a> in <a href="https://redirect.github.com/actions/upload-artifact/pull/764">actions/upload-artifact#764</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/Link"><code>@Link</code></a>- made their first contribution in <a href="https://redirect.github.com/actions/upload-artifact/pull/754">actions/upload-artifact#754</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/upload-artifact/compare/v6...v7.0.0">https://github.com/actions/upload-artifact/compare/v6...v7.0.0</a></p> <h2>v6.0.0</h2> <h2>v6 - What's new</h2> <blockquote> <p>[!IMPORTANT] actions/upload-artifact@v6 now runs on Node.js 24 (<code>runs.using: node24</code>) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.</p> </blockquote> <h3>Node.js 24</h3> <p>This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.</p> <h2>What's Changed</h2> <ul> <li>Upload Artifact Node 24 support by <a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a href="https://redirect.github.com/actions/upload-artifact/pull/719">actions/upload-artifact#719</a></li> <li>fix: update <code>@actions/artifact</code> for Node.js 24 punycode deprecation by <a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a href="https://redirect.github.com/actions/upload-artifact/pull/744">actions/upload-artifact#744</a></li> <li>prepare release v6.0.0 for Node.js 24 support by <a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a href="https://redirect.github.com/actions/upload-artifact/pull/745">actions/upload-artifact#745</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0">https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0</a></p> <h2>v5.0.0</h2> <h2>What's Changed</h2> <p><strong>BREAKING CHANGE:</strong> this update supports Node <code>v24.x</code>. This is not a breaking change per-se but we're treating it as such.</p> <ul> <li>Update README.md by <a href="https://github.com/GhadimiR"><code>@GhadimiR</code></a> in <a href="https://redirect.github.com/actions/upload-artifact/pull/681">actions/upload-artifact#681</a></li> <li>Update README.md by <a href="https://github.com/nebuk89"><code>@nebuk89</code></a> in <a href="https://redirect.github.com/actions/upload-artifact/pull/712">actions/upload-artifact#712</a></li> <li>Readme: spell out the first use of GHES by <a href="https://github.com/danwkennedy"><code>@danwkennedy</code></a> in <a href="https://redirect.github.com/actions/upload-artifact/pull/727">actions/upload-artifact#727</a></li> <li>Update GHES guidance to include reference to Node 20 version by <a href="https://github.com/patrikpolyak"><code>@patrikpolyak</code></a> in <a href="https://redirect.github.com/actions/upload-artifact/pull/725">actions/upload-artifact#725</a></li> <li>Bump <code>@actions/artifact</code> to <code>v4.0.0</code></li> <li>Prepare <code>v5.0.0</code> by <a href="https://github.com/danwkennedy"><code>@danwkennedy</code></a> in <a href="https://redirect.github.com/actions/upload-artifact/pull/734">actions/upload-artifact#734</a></li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/actions/upload-artifact/commit/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"><code>043fb46</code></a> Merge pull request <a href="https://redirect.github.com/actions/upload-artifact/issues/797">#797</a> from actions/yacaovsnc/update-dependency</li> <li><a href="https://github.com/actions/upload-artifact/commit/634250c1388765ea7ed0f053e636f1f399000b94"><code>634250c</code></a> Include changes in typespec/ts-http-runtime 0.3.5</li> <li><a href="https://github.com/actions/upload-artifact/commit/e454baaac2be505c9450e11b8f3215c6fc023ce8"><code>e454baa</code></a> Readme: bump all the example versions to v7 (<a href="https://redirect.github.com/actions/upload-artifact/issues/796">#796</a>)</li> <li><a href="https://github.com/actions/upload-artifact/commit/74fad66b98a6d799dc004d3353ccd0e6f6b2530e"><code>74fad66</code></a> Update the readme with direct upload details (<a href="https://redirect.github.com/actions/upload-artifact/issues/795">#795</a>)</li> <li><a href="https://github.com/actions/upload-artifact/commit/bbbca2ddaa5d8feaa63e36b76fdaad77386f024f"><code>bbbca2d</code></a> Support direct file uploads (<a href="https://redirect.github.com/actions/upload-artifact/issues/764">#764</a>)</li> <li><a href="https://github.com/actions/upload-artifact/commit/589182c5a4cec8920b8c1bce3e2fab1c97a02296"><code>589182c</code></a> Upgrade the module to ESM and bump dependencies (<a href="https://redirect.github.com/actions/upload-artifact/issues/762">#762</a>)</li> <li><a href="https://github.com/actions/upload-artifact/commit/47309c993abb98030a35d55ef7ff34b7fa1074b5"><code>47309c9</code></a> Merge pull request <a href="https://redirect.github.com/actions/upload-artifact/issues/754">#754</a> from actions/Link-/add-proxy-integration-tests</li> <li><a href="https://github.com/actions/upload-artifact/commit/02a8460834e70dab0ce194c64360c59dc1475ef0"><code>02a8460</code></a> Add proxy integration test</li> <li><a href="https://github.com/actions/upload-artifact/commit/b7c566a772e6b6bfb58ed0dc250532a479d7789f"><code>b7c566a</code></a> Merge pull request <a href="https://redirect.github.com/actions/upload-artifact/issues/745">#745</a> from actions/upload-artifact-v6-release</li> <li><a href="https://github.com/actions/upload-artifact/commit/e516bc8500aaf3d07d591fcd4ae6ab5f9c391d5b"><code>e516bc8</code></a> docs: correct description of Node.js 24 support in README</li> <li>Additional commits viewable in <a href="https://github.com/actions/upload-artifact/compare/v4...v7">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…566) Bumps the nextjs group with 2 updates in the / directory: [next](https://github.com/vercel/next.js) and [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next). Updates `next` from 16.3.4 to 16.3.5 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/vercel/next.js/releases">next's releases</a>.</em></p> <blockquote> <h2>v16.3.5</h2> <p>The following bug fixes have been backported. It does not include all pending features/changes on canary.</p> <ul> <li>next/image: Skip 0-byte entries when initializing disk LRU cache (<a href="https://redirect.github.com/vercel/next.js/issues/98185">#98185</a>)</li> <li>next/image: Reject empty images when reading/writing to the disk cache (<a href="https://redirect.github.com/vercel/next.js/issues/98186">#98186</a>)</li> <li>Emit whole-app server NFTs when <code>output: 'standalone'</code> is used with an adapter (<a href="https://redirect.github.com/vercel/next.js/issues/98167">#98167</a>)</li> <li>Add CSP nonce to script tags of loading and template files (<a href="https://redirect.github.com/vercel/next.js/issues/98403">#98403</a>)</li> <li>Fix <code>use cache</code> prerender signal retention (<a href="https://redirect.github.com/vercel/next.js/issues/98448">#98448</a>)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/vercel/next.js/commit/ca2c75eb7f8d9dd012a8bb83c06132149fe221f9"><code>ca2c75e</code></a> v16.3.5</li> <li><a href="https://github.com/vercel/next.js/commit/14fb290de65077e9f1e22ef56d8de6cc1e63d436"><code>14fb290</code></a> [backport] Fix use cache prerender signal retention (<a href="https://redirect.github.com/vercel/next.js/issues/98448">#98448</a>)</li> <li><a href="https://github.com/vercel/next.js/commit/2b1f28dbe1de344807ec0946a85171bc890a6047"><code>2b1f28d</code></a> [16.3.x] Add CSP nonce to script tags of loading and template files (<a href="https://redirect.github.com/vercel/next.js/issues/98403">#98403</a>)</li> <li><a href="https://github.com/vercel/next.js/commit/4b56cee3f01d3b249edcd798b51895d5126a4170"><code>4b56cee</code></a> [16.3.x] Backport docs fixes (<a href="https://redirect.github.com/vercel/next.js/issues/98317">#98317</a>)</li> <li><a href="https://github.com/vercel/next.js/commit/5568a02a7d47f9cb088e58350f2c2e68d9e93a00"><code>5568a02</code></a> [backport] docs: local development: Rewrite docker section, add Windows Dev D...</li> <li><a href="https://github.com/vercel/next.js/commit/93249ab2144132abfd4a8d611dad5b5978107ee2"><code>93249ab</code></a> [16.3.X] Emit whole-app server NFTs when <code>output: 'standalone'</code> is used with ...</li> <li><a href="https://github.com/vercel/next.js/commit/6549fd7c4e15a8883b0ad1c2ef67dec287a44f12"><code>6549fd7</code></a> [16.3.x] next/image: reject empty image on read/write to disk cache (<a href="https://redirect.github.com/vercel/next.js/issues/98186">#98186</a>)</li> <li><a href="https://github.com/vercel/next.js/commit/d9eac96e7526ff0b9cb51db9801f06e741fe1960"><code>d9eac96</code></a> [16.3.x] next/image: skip 0-byte entries when initializing disk LRU cache (<a href="https://redirect.github.com/vercel/next.js/issues/9">#9</a>...</li> <li><a href="https://github.com/vercel/next.js/commit/84b35feccb2b53a563e41ad2dfe7a5fe08c58d3f"><code>84b35fe</code></a> [test] Fix 16.3 deploy test assertions (<a href="https://redirect.github.com/vercel/next.js/issues/98133">#98133</a>)</li> <li><a href="https://github.com/vercel/next.js/commit/14f9c1ac4e084a44633c354476ddeaf70969cd90"><code>14f9c1a</code></a> [16.3.x][ci] Run flake detection and new deploy tests when merged and on back...</li> <li>See full diff in <a href="https://github.com/vercel/next.js/compare/v16.3.4...v16.3.5">compare view</a></li> </ul> </details> <br /> Updates `eslint-config-next` from 16.3.4 to 16.3.5 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/vercel/next.js/releases">eslint-config-next's releases</a>.</em></p> <blockquote> <h2>v16.3.5</h2> <p>The following bug fixes have been backported. It does not include all pending features/changes on canary.</p> <ul> <li>next/image: Skip 0-byte entries when initializing disk LRU cache (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/98185">#98185</a>)</li> <li>next/image: Reject empty images when reading/writing to the disk cache (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/98186">#98186</a>)</li> <li>Emit whole-app server NFTs when <code>output: 'standalone'</code> is used with an adapter (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/98167">#98167</a>)</li> <li>Add CSP nonce to script tags of loading and template files (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/98403">#98403</a>)</li> <li>Fix <code>use cache</code> prerender signal retention (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/98448">#98448</a>)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/vercel/next.js/commit/ca2c75eb7f8d9dd012a8bb83c06132149fe221f9"><code>ca2c75e</code></a> v16.3.5</li> <li>See full diff in <a href="https://github.com/vercel/next.js/commits/v16.3.5/packages/eslint-config-next">compare view</a></li> </ul> </details> <br /> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.62.1 to 1.63.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/microsoft/playwright/releases">@playwright/test's releases</a>.</em></p> <blockquote> <h2>v1.63.0</h2> <h2>🔒 Test locks</h2> <p>Tests that access a shared resource — an external service, a global account setting — can now declare a named <code>lock</code>. Tests that share a lock name never run concurrently, across files, workers and <a href="https://playwright.dev/docs/test-projects">projects</a>, while everything else keeps running in parallel:</p> <pre lang="js"><code>test('update user settings', { lock: 'user-settings' }, async ({ page }) => { // never runs at the same time as other tests holding 'user-settings' }); </code></pre> <p>A test can hold multiple locks, and <a href="https://playwright.dev/docs/api/class-test#test-describe">test.describe()</a> accepts a <code>lock</code> for the whole group. Learn more about <a href="https://playwright.dev/docs/test-parallel#test-locks">test locks</a>.</p> <h2>🪟 Locate across frames</h2> <p><a href="https://playwright.dev/docs/api/class-page#page-frame-locator">page.frameLocator()</a> and <a href="https://playwright.dev/docs/api/class-frame#frame-frame-locator">frame.frameLocator()</a> called without a selector search in any frame of the subtree, so you no longer need to locate the iframe first:</p> <pre lang="js"><code>// Finds the button in any frame on the page. await page.frameLocator().getByRole('button').click(); </code></pre> <p>The rest of the locator resolves inside a single frame, just like a regular locator, and an error is thrown when it matches elements in several frames.</p> <h2>👁️ Visible-only locators</h2> <p>New <a href="https://playwright.dev/docs/api/class-locator#locator-visible">locator.visible()</a> returns a locator that matches only visible elements. It is the recommended replacement for the <code>:visible</code> CSS pseudo-class:</p> <pre lang="js"><code>await page.locator('button').visible().click(); </code></pre> <h2>🧾 Step params and subtitles</h2> <p>Steps now carry structured data for reporters. Playwright API steps report the target locator and call arguments, and <a href="https://playwright.dev/docs/api/class-test#test-step">test.step()</a> accepts <code>subtitle</code> and <code>params</code> options for your own steps:</p> <pre lang="js"><code>await test.step('Login', async () => { // ... }, { subtitle: 'as admin', params: { user: 'admin' } }); </code></pre> <p>Reporters receive them via <a href="https://playwright.dev/docs/api/class-teststep#test-step-subtitle">testStep.subtitle</a> and <a href="https://playwright.dev/docs/api/class-teststep#test-step-params">testStep.params</a>. For Playwright API</p> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/microsoft/playwright/commit/1b025d7e20a026371cd5f98ba0cdce48892737c8"><code>1b025d7</code></a> chore: mark v1.63.0 (<a href="https://redirect.github.com/microsoft/playwright/issues/42569">#42569</a>)</li> <li><a href="https://github.com/microsoft/playwright/commit/0b9956d2247ce88399c3c46a22c1cc0526acb340"><code>0b9956d</code></a> cherry-pick(<a href="https://redirect.github.com/microsoft/playwright/issues/42568">#42568</a>): docs(test): mark test.step subtitle option as since v1.63</li> <li><a href="https://github.com/microsoft/playwright/commit/13dbf102b88305ed96b7791a7843246cd75d4dae"><code>13dbf10</code></a> cherry-pick(<a href="https://redirect.github.com/microsoft/playwright/issues/42552">#42552</a>): docs: release notes for v1.63</li> <li><a href="https://github.com/microsoft/playwright/commit/e93b64ed6f9cf6ac2cc8cb13ad01514b406a7142"><code>e93b64e</code></a> cherry-pick(<a href="https://redirect.github.com/microsoft/playwright/issues/42566">#42566</a>): feat(test): add subtitle option to test.step (<a href="https://redirect.github.com/microsoft/playwright/issues/42567">#42567</a>)</li> <li><a href="https://github.com/microsoft/playwright/commit/2b7a5f2ea1cea04b549c5378498c1b16b6bc6a6f"><code>2b7a5f2</code></a> test: response.body() for content-encoding:identity (<a href="https://redirect.github.com/microsoft/playwright/issues/42537">#42537</a>)</li> <li><a href="https://github.com/microsoft/playwright/commit/648a67c7c1261eefe4113cba2d586417d5e3f2f2"><code>648a67c</code></a> fix(mcp): create parent directories for explicitly named files (<a href="https://redirect.github.com/microsoft/playwright/issues/42540">#42540</a>)</li> <li><a href="https://github.com/microsoft/playwright/commit/7894f5653e7f253c17aa1638c19f63e0d54d5e0e"><code>7894f56</code></a> docs(mcp): clarify how tool file names are resolved (<a href="https://redirect.github.com/microsoft/playwright/issues/42538">#42538</a>)</li> <li><a href="https://github.com/microsoft/playwright/commit/52900a1b99ca51011526afcaf8981970cc1a521a"><code>52900a1</code></a> devops: restore npm publishing from GitHub Actions (<a href="https://redirect.github.com/microsoft/playwright/issues/42550">#42550</a>)</li> <li><a href="https://github.com/microsoft/playwright/commit/8c47f596edebd2460895a5953f2ea67f8b884001"><code>8c47f59</code></a> docs(csharp): fix nonexistent method names in guide examples (<a href="https://redirect.github.com/microsoft/playwright/issues/42507">#42507</a>)</li> <li><a href="https://github.com/microsoft/playwright/commit/bd6e552a63f8cf9b0da0216f392b8e63a08bf1d2"><code>bd6e552</code></a> chore(video): emit frames with real timestamps, drop frame number quantizatio...</li> <li>Additional commits viewable in <a href="https://github.com/microsoft/playwright/compare/v1.62.1...v1.63.0">compare view</a></li> </ul> </details> <br /> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…574) Bumps the react group with 4 updates in the / directory: [react](https://github.com/react/react/tree/HEAD/packages/react), [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react), [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom). Updates `react` from 19.2.8 to 19.3.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/react/react/releases">react's releases</a>.</em></p> <blockquote> <h2>19.3.0 (September 9, 2026)</h2> <p>Below is a list of all new features, APIs, and bug fixes.</p> <p>Read the <a href="https://react.dev/blog/2026/09/09/react-19-3">React 19.3 release post</a> for more information.</p> <h2>New React Features</h2> <ul> <li><code><ViewTransition /></code>: Adds <code><ViewTransition /></code> and <code>addTransitionType</code> APIs to power View Transition animations in React (<a href="https://github.com/sebmarkbage"><code>@sebmarkbage</code></a>, <a href="https://github.com/jackpope"><code>@jackpope</code></a>, <a href="https://github.com/gaearon"><code>@gaearon</code></a>: <a href="https://redirect.github.com/facebook/react/pull/31975">#31975</a>, <a href="https://redirect.github.com/facebook/react/pull/31987">#31987</a>, <a href="https://redirect.github.com/facebook/react/pull/31996">#31996</a>, <a href="https://redirect.github.com/facebook/react/pull/31999">#31999</a>, <a href="https://redirect.github.com/facebook/react/pull/32001">#32001</a>, <a href="https://redirect.github.com/facebook/react/pull/32002">#32002</a>, <a href="https://redirect.github.com/facebook/react/pull/32028">#32028</a>, <a href="https://redirect.github.com/facebook/react/pull/32029">#32029</a>, <a href="https://redirect.github.com/facebook/react/pull/32031">#32031</a>, <a href="https://redirect.github.com/facebook/react/pull/32034">#32034</a>, <a href="https://redirect.github.com/facebook/react/pull/32038">#32038</a>, <a href="https://redirect.github.com/facebook/react/pull/32041">#32041</a>, <a href="https://redirect.github.com/facebook/react/pull/32050">#32050</a>, <a href="https://redirect.github.com/facebook/react/pull/32090">#32090</a>, <a href="https://redirect.github.com/facebook/react/pull/32105">#32105</a>, <a href="https://redirect.github.com/facebook/react/pull/32254">#32254</a>, <a href="https://redirect.github.com/facebook/react/pull/32379">#32379</a>, <a href="https://redirect.github.com/facebook/react/pull/32422">#32422</a>, <a href="https://redirect.github.com/facebook/react/pull/32462">#32462</a>, <a href="https://redirect.github.com/facebook/react/pull/32540">#32540</a>, <a href="https://redirect.github.com/facebook/react/pull/32545">#32545</a>, <a href="https://redirect.github.com/facebook/react/pull/32585">#32585</a>, <a href="https://redirect.github.com/facebook/react/pull/32599">#32599</a>, <a href="https://redirect.github.com/facebook/react/pull/32611">#32611</a>, <a href="https://redirect.github.com/facebook/react/pull/32612">#32612</a>, <a href="https://redirect.github.com/facebook/react/pull/32617">#32617</a>, <a href="https://redirect.github.com/facebook/react/pull/32651">#32651</a>, <a href="https://redirect.github.com/facebook/react/pull/32653">#32653</a>, <a href="https://redirect.github.com/facebook/react/pull/32656">#32656</a>, <a href="https://redirect.github.com/facebook/react/pull/32664">#32664</a>, <a href="https://redirect.github.com/facebook/react/pull/32699">#32699</a>, <a href="https://redirect.github.com/facebook/react/pull/32723">#32723</a>, <a href="https://redirect.github.com/facebook/react/pull/32734">#32734</a>, <a href="https://redirect.github.com/facebook/react/pull/32751">#32751</a>, <a href="https://redirect.github.com/facebook/react/pull/32752">#32752</a>, <a href="https://redirect.github.com/facebook/react/pull/32760">#32760</a>, <a href="https://redirect.github.com/facebook/react/pull/32761">#32761</a>, <a href="https://redirect.github.com/facebook/react/pull/32764">#32764</a>, <a href="https://redirect.github.com/facebook/react/pull/32772">#32772</a>, <a href="https://redirect.github.com/facebook/react/pull/32790">#32790</a>, <a href="https://redirect.github.com/facebook/react/pull/32819">#32819</a>, <a href="https://redirect.github.com/facebook/react/pull/32820">#32820</a>, <a href="https://redirect.github.com/facebook/react/pull/32822">#32822</a>, <a href="https://redirect.github.com/facebook/react/pull/32833">#32833</a>, <a href="https://redirect.github.com/facebook/react/pull/32849">#32849</a>, <a href="https://redirect.github.com/facebook/react/pull/33094">#33094</a>, <a href="https://redirect.github.com/facebook/react/pull/33191">#33191</a>, <a href="https://redirect.github.com/facebook/react/pull/33200">#33200</a>, <a href="https://redirect.github.com/facebook/react/pull/33206">#33206</a>, <a href="https://redirect.github.com/facebook/react/pull/33293">#33293</a>, <a href="https://redirect.github.com/facebook/react/pull/33330">#33330</a>, <a href="https://redirect.github.com/facebook/react/pull/33331">#33331</a>, <a href="https://redirect.github.com/facebook/react/pull/33332">#33332</a>, <a href="https://redirect.github.com/facebook/react/pull/33357">#33357</a>, <a href="https://redirect.github.com/facebook/react/pull/33362">#33362</a>, <a href="https://redirect.github.com/facebook/react/pull/33433">#33433</a>, <a href="https://redirect.github.com/facebook/react/pull/33576">#33576</a>, <a href="https://redirect.github.com/facebook/react/pull/34374">#34374</a>, <a href="https://redirect.github.com/facebook/react/pull/34450">#34450</a>, <a href="https://redirect.github.com/facebook/react/pull/34481">#34481</a>, <a href="https://redirect.github.com/facebook/react/pull/34500">#34500</a>, <a href="https://redirect.github.com/facebook/react/pull/34502">#34502</a>, <a href="https://redirect.github.com/facebook/react/pull/34510">#34510</a>, <a href="https://redirect.github.com/facebook/react/pull/34511">#34511</a>, <a href="https://redirect.github.com/facebook/react/pull/34539">#34539</a>, <a href="https://redirect.github.com/facebook/react/pull/35567">#35567</a>, <a href="https://redirect.github.com/facebook/react/pull/35564">#35564</a>, <a href="https://redirect.github.com/facebook/react/pull/35485">#35485</a>, <a href="https://redirect.github.com/facebook/react/pull/35380">#35380</a>, <a href="https://redirect.github.com/facebook/react/pull/35063">#35063</a>, <a href="https://redirect.github.com/facebook/react/pull/35060">#35060</a>, <a href="https://redirect.github.com/facebook/react/pull/34676">#34676</a>, <a href="https://redirect.github.com/facebook/react/pull/36917">#36917</a>, <a href="https://redirect.github.com/facebook/react/pull/35337">#35337</a>, <a href="https://redirect.github.com/facebook/react/pull/35520">#35520</a>)</li> <li>Fragment Refs: Add Refs to <code><Fragment /></code> to support composable platform behavior (<a href="https://github.com/jackpope"><code>@jackpope</code></a>, <a href="https://github.com/sebmarkbage"><code>@sebmarkbage</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>, <a href="https://github.com/Dhakshin2007"><code>@Dhakshin2007</code></a>, <a href="https://github.com/chirokas"><code>@chirokas</code></a>, <a href="https://github.com/teamleaderleo"><code>@teamleaderleo</code></a>, <a href="https://github.com/fallintoplace"><code>@fallintoplace</code></a>: <a href="https://redirect.github.com/facebook/react/pull/32465">#32465</a>, <a href="https://redirect.github.com/facebook/react/pull/32613">#32613</a>, <a href="https://redirect.github.com/facebook/react/pull/32619">#32619</a>, <a href="https://redirect.github.com/facebook/react/pull/32654">#32654</a>, <a href="https://redirect.github.com/facebook/react/pull/32660">#32660</a>, <a href="https://redirect.github.com/facebook/react/pull/32682">#32682</a>, <a href="https://redirect.github.com/facebook/react/pull/32722">#32722</a>, <a href="https://redirect.github.com/facebook/react/pull/32813">#32813</a>, <a href="https://redirect.github.com/facebook/react/pull/32814">#32814</a>, <a href="https://redirect.github.com/facebook/react/pull/33056">#33056</a>, <a href="https://redirect.github.com/facebook/react/pull/33058">#33058</a>, <a href="https://redirect.github.com/facebook/react/pull/33093">#33093</a>, <a href="https://redirect.github.com/facebook/react/pull/34069">#34069</a>, <a href="https://redirect.github.com/facebook/react/pull/34103">#34103</a>, <a href="https://redirect.github.com/facebook/react/pull/34544">#34544</a>, <a href="https://redirect.github.com/facebook/react/pull/34545">#34545</a>, <a href="https://redirect.github.com/facebook/react/pull/37062">#37062</a>, <a href="https://redirect.github.com/facebook/react/pull/37061">#37061</a>, <a href="https://redirect.github.com/facebook/react/pull/37060">#37060</a>, <a href="https://redirect.github.com/facebook/react/pull/36047">#36047</a>, <a href="https://redirect.github.com/facebook/react/pull/36010">#36010</a>, <a href="https://redirect.github.com/facebook/react/pull/35642">#35642</a>, <a href="https://redirect.github.com/facebook/react/pull/35641">#35641</a>, <a href="https://redirect.github.com/facebook/react/pull/35637">#35637</a>, <a href="https://redirect.github.com/facebook/react/pull/35630">#35630</a>, <a href="https://redirect.github.com/facebook/react/pull/34935">#34935</a>, <a href="https://redirect.github.com/facebook/react/pull/37457">#37457</a>, <a href="https://redirect.github.com/facebook/react/pull/37408">#37408</a>, <a href="https://redirect.github.com/facebook/react/pull/37326">#37326</a>, <a href="https://redirect.github.com/facebook/react/pull/37251">#37251</a>, <a href="https://redirect.github.com/facebook/react/pull/37171">#37171</a>, <a href="https://redirect.github.com/facebook/react/pull/37169">#37169</a>, <a href="https://redirect.github.com/facebook/react/pull/37168">#37168</a>, <a href="https://redirect.github.com/facebook/react/pull/37167">#37167</a>, <a href="https://redirect.github.com/facebook/react/pull/37166">#37166</a>, <a href="https://redirect.github.com/facebook/react/pull/37165">#37165</a>, <a href="https://redirect.github.com/facebook/react/pull/37164">#37164</a>, <a href="https://redirect.github.com/facebook/react/pull/37163">#37163</a>, <a href="https://redirect.github.com/facebook/react/pull/37162">#37162</a>, <a href="https://redirect.github.com/facebook/react/pull/37161">#37161</a>, <a href="https://redirect.github.com/facebook/react/pull/37160">#37160</a>, <a href="https://redirect.github.com/facebook/react/pull/37125">#37125</a>, <a href="https://redirect.github.com/facebook/react/pull/37063">#37063</a>)</li> </ul> <h2>New React DOM Features</h2> <ul> <li><code>browser()</code>: a new <code>react-dom</code> API that returns a usable which errors during server rendering and resolves in the browser. <code>use(browser())</code> inside a <code><Suspense></code> boundary marks a subtree as browser-only without reporting a recoverable error (<a href="https://github.com/gnoff"><code>@gnoff</code></a>: <a href="https://redirect.github.com/facebook/react/pull/37143">#37143</a>, <a href="https://redirect.github.com/facebook/react/pull/37241">#37241</a>) <ul> <li>Added an <code>onBrowserBailout</code> option to the <code>react-dom/server</code> APIs to observe when a subtree defers to the browser (<a href="https://github.com/gnoff"><code>@gnoff</code></a> <a href="https://redirect.github.com/facebook/react/pull/37193">#37193</a>)</li> </ul> </li> </ul> <h2>Notable changes</h2> <ul> <li>Enable Trusted Types API integration (<a href="https://github.com/rickhanlonii"><code>@rickhanlonii</code></a> <a href="https://redirect.github.com/facebook/react/pull/35816">#35816</a>)</li> <li>Transitions now render independently instead of being entangled into a single render, so a slow transition no longer holds up unrelated ones (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li> <li>Added a DEV-only warning when a component appears to have been unblocked by calling <code>use()</code> conditionally (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>: <a href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>, <a href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>, <a href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li> </ul> <h2>All Changes</h2> <h3>React</h3> <ul> <li>Fast Refresh Fixes <ul> <li>Fix Fast Refresh to find and remount edits to components wrapped behind <code>lazy()</code> (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36965">#36965</a>)</li> <li>Fix Fast Refresh so edits to a <code>memo()</code> comparison function take effect (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36964">#36964</a>)</li> <li>Fix Fast Refresh crash when an edit changes the kind of a component's type (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36963">#36963</a>)</li> <li>Unify hot reload type resolution for Fast Refresh (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36962">#36962</a>)</li> <li>Fix Fast Refresh to remount correctly when an edit changes the component kind (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36950">#36950</a>)</li> <li>Double invoke effects in StrictMode after Fast Refresh (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35962">#35962</a>)</li> </ul> </li> <li>Performance Track Fixes <ul> <li>Prevent crash when accessing <code>$$typeof</code> in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35679">#35679</a>)</li> <li>Handle non-string function names in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35659">#35659</a>)</li> <li>Use minus (<code>-</code>) instead of en dash for removed props in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35649">#35649</a>)</li> <li>Handle arrays with bigints in deep objects in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35648">#35648</a>)</li> <li>Don't enumerate typed array props in Performance Tracks in DEV (<a href="https://github.com/UditDewan"><code>@UditDewan</code></a> <a href="https://redirect.github.com/facebook/react/pull/36913">#36913</a>)</li> <li>Bail out of diffing wide objects and arrays in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/34742">#34742</a>)</li> <li>Clear potentially large performance measures in DEV (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a> <a href="https://redirect.github.com/facebook/react/pull/34803">#34803</a>)</li> <li>Fix missing else branch for renders with no props change in Performance Tracks (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a> <a href="https://redirect.github.com/facebook/react/pull/34837">#34837</a>)</li> </ul> </li> <li>Activity Fixes <ul> <li>Fix <code>useSyncExternalStore</code> missing store mutations that happened while an Activity tree was hidden (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36947">#36947</a>)</li> <li>Hide portal contents when an Activity is hidden (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/35091">#35091</a>)</li> <li>Prevent metadata hoisting in hidden <code><Activity></code> trees (<a href="https://github.com/ronnakamoto"><code>@ronnakamoto</code></a> <a href="https://redirect.github.com/facebook/react/pull/34983">#34983</a>)</li> <li>Prevent errors thrown inside a hidden Activity from escaping to the visible UI (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/35074">#35074</a>)</li> <li>Don't unhide a node if a direct parent Offscreen is still hidden (<a href="https://github.com/sebmarkbage"><code>@sebmarkbage</code></a> <a href="https://redirect.github.com/facebook/react/pull/34821">#34821</a>)</li> <li>Don't show internal <code><Offscreen></code> component in error messages (<a href="https://github.com/rickhanlonii"><code>@rickhanlonii</code></a> <a href="https://redirect.github.com/facebook/react/pull/35763">#35763</a>)</li> </ul> </li> <li>Warn in DEV when a component appears to have been unblocked by a conditional <code>use()</code> (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>: <a href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>, <a href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>, <a href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li> <li>Render transitions independently instead of entangling them into a single render (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/react/react/blob/main/CHANGELOG.md">react's changelog</a>.</em></p> <blockquote> <h2>19.3.0 (September 9, 2026)</h2> <h3>New React Features</h3> <ul> <li><code><ViewTransition /></code>: Adds <code><ViewTransition /></code> and <code>addTransitionType</code> APIs to power View Transition animations in React (<a href="https://github.com/sebmarkbage"><code>@sebmarkbage</code></a>, <a href="https://github.com/jackpope"><code>@jackpope</code></a>, <a href="https://github.com/gaearon"><code>@gaearon</code></a>: <a href="https://redirect.github.com/facebook/react/pull/31975">#31975</a>, <a href="https://redirect.github.com/facebook/react/pull/31987">#31987</a>, <a href="https://redirect.github.com/facebook/react/pull/31996">#31996</a>, <a href="https://redirect.github.com/facebook/react/pull/31999">#31999</a>, <a href="https://redirect.github.com/facebook/react/pull/32001">#32001</a>, <a href="https://redirect.github.com/facebook/react/pull/32002">#32002</a>, <a href="https://redirect.github.com/facebook/react/pull/32028">#32028</a>, <a href="https://redirect.github.com/facebook/react/pull/32029">#32029</a>, <a href="https://redirect.github.com/facebook/react/pull/32031">#32031</a>, <a href="https://redirect.github.com/facebook/react/pull/32034">#32034</a>, <a href="https://redirect.github.com/facebook/react/pull/32038">#32038</a>, <a href="https://redirect.github.com/facebook/react/pull/32041">#32041</a>, <a href="https://redirect.github.com/facebook/react/pull/32050">#32050</a>, <a href="https://redirect.github.com/facebook/react/pull/32090">#32090</a>, <a href="https://redirect.github.com/facebook/react/pull/32105">#32105</a>, <a href="https://redirect.github.com/facebook/react/pull/32254">#32254</a>, <a href="https://redirect.github.com/facebook/react/pull/32379">#32379</a>, <a href="https://redirect.github.com/facebook/react/pull/32422">#32422</a>, <a href="https://redirect.github.com/facebook/react/pull/32462">#32462</a>, <a href="https://redirect.github.com/facebook/react/pull/32540">#32540</a>, <a href="https://redirect.github.com/facebook/react/pull/32545">#32545</a>, <a href="https://redirect.github.com/facebook/react/pull/32585">#32585</a>, <a href="https://redirect.github.com/facebook/react/pull/32599">#32599</a>, <a href="https://redirect.github.com/facebook/react/pull/32611">#32611</a>, <a href="https://redirect.github.com/facebook/react/pull/32612">#32612</a>, <a href="https://redirect.github.com/facebook/react/pull/32617">#32617</a>, <a href="https://redirect.github.com/facebook/react/pull/32651">#32651</a>, <a href="https://redirect.github.com/facebook/react/pull/32653">#32653</a>, <a href="https://redirect.github.com/facebook/react/pull/32656">#32656</a>, <a href="https://redirect.github.com/facebook/react/pull/32664">#32664</a>, <a href="https://redirect.github.com/facebook/react/pull/32699">#32699</a>, <a href="https://redirect.github.com/facebook/react/pull/32723">#32723</a>, <a href="https://redirect.github.com/facebook/react/pull/32734">#32734</a>, <a href="https://redirect.github.com/facebook/react/pull/32751">#32751</a>, <a href="https://redirect.github.com/facebook/react/pull/32752">#32752</a>, <a href="https://redirect.github.com/facebook/react/pull/32760">#32760</a>, <a href="https://redirect.github.com/facebook/react/pull/32761">#32761</a>, <a href="https://redirect.github.com/facebook/react/pull/32764">#32764</a>, <a href="https://redirect.github.com/facebook/react/pull/32772">#32772</a>, <a href="https://redirect.github.com/facebook/react/pull/32790">#32790</a>, <a href="https://redirect.github.com/facebook/react/pull/32819">#32819</a>, <a href="https://redirect.github.com/facebook/react/pull/32820">#32820</a>, <a href="https://redirect.github.com/facebook/react/pull/32822">#32822</a>, <a href="https://redirect.github.com/facebook/react/pull/32833">#32833</a>, <a href="https://redirect.github.com/facebook/react/pull/32849">#32849</a>, <a href="https://redirect.github.com/facebook/react/pull/33094">#33094</a>, <a href="https://redirect.github.com/facebook/react/pull/33191">#33191</a>, <a href="https://redirect.github.com/facebook/react/pull/33200">#33200</a>, <a href="https://redirect.github.com/facebook/react/pull/33206">#33206</a>, <a href="https://redirect.github.com/facebook/react/pull/33293">#33293</a>, <a href="https://redirect.github.com/facebook/react/pull/33330">#33330</a>, <a href="https://redirect.github.com/facebook/react/pull/33331">#33331</a>, <a href="https://redirect.github.com/facebook/react/pull/33332">#33332</a>, <a href="https://redirect.github.com/facebook/react/pull/33357">#33357</a>, <a href="https://redirect.github.com/facebook/react/pull/33362">#33362</a>, <a href="https://redirect.github.com/facebook/react/pull/33433">#33433</a>, <a href="https://redirect.github.com/facebook/react/pull/33576">#33576</a>, <a href="https://redirect.github.com/facebook/react/pull/34374">#34374</a>, <a href="https://redirect.github.com/facebook/react/pull/34450">#34450</a>, <a href="https://redirect.github.com/facebook/react/pull/34481">#34481</a>, <a href="https://redirect.github.com/facebook/react/pull/34500">#34500</a>, <a href="https://redirect.github.com/facebook/react/pull/34502">#34502</a>, <a href="https://redirect.github.com/facebook/react/pull/34510">#34510</a>, <a href="https://redirect.github.com/facebook/react/pull/34511">#34511</a>, <a href="https://redirect.github.com/facebook/react/pull/34539">#34539</a>, <a href="https://redirect.github.com/facebook/react/pull/35567">#35567</a>, <a href="https://redirect.github.com/facebook/react/pull/35564">#35564</a>, <a href="https://redirect.github.com/facebook/react/pull/35485">#35485</a>, <a href="https://redirect.github.com/facebook/react/pull/35380">#35380</a>, <a href="https://redirect.github.com/facebook/react/pull/35063">#35063</a>, <a href="https://redirect.github.com/facebook/react/pull/35060">#35060</a>, <a href="https://redirect.github.com/facebook/react/pull/34676">#34676</a>, <a href="https://redirect.github.com/facebook/react/pull/36917">#36917</a>, <a href="https://redirect.github.com/facebook/react/pull/35337">#35337</a>, <a href="https://redirect.github.com/facebook/react/pull/35520">#35520</a>)</li> <li>Fragment Refs: Add Refs to <code><Fragment /></code> to support composable platform behavior (<a href="https://github.com/jackpope"><code>@jackpope</code></a>, <a href="https://github.com/sebmarkbage"><code>@sebmarkbage</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>, <a href="https://github.com/Dhakshin2007"><code>@Dhakshin2007</code></a>, <a href="https://github.com/chirokas"><code>@chirokas</code></a>, <a href="https://github.com/teamleaderleo"><code>@teamleaderleo</code></a>, <a href="https://github.com/fallintoplace"><code>@fallintoplace</code></a>: <a href="https://redirect.github.com/facebook/react/pull/32465">#32465</a>, <a href="https://redirect.github.com/facebook/react/pull/32613">#32613</a>, <a href="https://redirect.github.com/facebook/react/pull/32619">#32619</a>, <a href="https://redirect.github.com/facebook/react/pull/32654">#32654</a>, <a href="https://redirect.github.com/facebook/react/pull/32660">#32660</a>, <a href="https://redirect.github.com/facebook/react/pull/32682">#32682</a>, <a href="https://redirect.github.com/facebook/react/pull/32722">#32722</a>, <a href="https://redirect.github.com/facebook/react/pull/32813">#32813</a>, <a href="https://redirect.github.com/facebook/react/pull/32814">#32814</a>, <a href="https://redirect.github.com/facebook/react/pull/33056">#33056</a>, <a href="https://redirect.github.com/facebook/react/pull/33058">#33058</a>, <a href="https://redirect.github.com/facebook/react/pull/33093">#33093</a>, <a href="https://redirect.github.com/facebook/react/pull/34069">#34069</a>, <a href="https://redirect.github.com/facebook/react/pull/34103">#34103</a>, <a href="https://redirect.github.com/facebook/react/pull/34544">#34544</a>, <a href="https://redirect.github.com/facebook/react/pull/34545">#34545</a>, <a href="https://redirect.github.com/facebook/react/pull/37062">#37062</a>, <a href="https://redirect.github.com/facebook/react/pull/37061">#37061</a>, <a href="https://redirect.github.com/facebook/react/pull/37060">#37060</a>, <a href="https://redirect.github.com/facebook/react/pull/36047">#36047</a>, <a href="https://redirect.github.com/facebook/react/pull/36010">#36010</a>, <a href="https://redirect.github.com/facebook/react/pull/35642">#35642</a>, <a href="https://redirect.github.com/facebook/react/pull/35641">#35641</a>, <a href="https://redirect.github.com/facebook/react/pull/35637">#35637</a>, <a href="https://redirect.github.com/facebook/react/pull/35630">#35630</a>, <a href="https://redirect.github.com/facebook/react/pull/34935">#34935</a>, <a href="https://redirect.github.com/facebook/react/pull/37457">#37457</a>, <a href="https://redirect.github.com/facebook/react/pull/37408">#37408</a>, <a href="https://redirect.github.com/facebook/react/pull/37326">#37326</a>, <a href="https://redirect.github.com/facebook/react/pull/37251">#37251</a>, <a href="https://redirect.github.com/facebook/react/pull/37171">#37171</a>, <a href="https://redirect.github.com/facebook/react/pull/37169">#37169</a>, <a href="https://redirect.github.com/facebook/react/pull/37168">#37168</a>, <a href="https://redirect.github.com/facebook/react/pull/37167">#37167</a>, <a href="https://redirect.github.com/facebook/react/pull/37166">#37166</a>, <a href="https://redirect.github.com/facebook/react/pull/37165">#37165</a>, <a href="https://redirect.github.com/facebook/react/pull/37164">#37164</a>, <a href="https://redirect.github.com/facebook/react/pull/37163">#37163</a>, <a href="https://redirect.github.com/facebook/react/pull/37162">#37162</a>, <a href="https://redirect.github.com/facebook/react/pull/37161">#37161</a>, <a href="https://redirect.github.com/facebook/react/pull/37160">#37160</a>, <a href="https://redirect.github.com/facebook/react/pull/37125">#37125</a>, <a href="https://redirect.github.com/facebook/react/pull/37063">#37063</a>)</li> </ul> <h3>New React DOM Features</h3> <ul> <li><code>browser()</code>: a new <code>react-dom</code> API that returns a usable which errors during server rendering and resolves in the browser. <code>use(browser())</code> inside a <code><Suspense></code> boundary marks a subtree as browser-only without reporting a recoverable error (<a href="https://github.com/gnoff"><code>@gnoff</code></a>: <a href="https://redirect.github.com/facebook/react/pull/37143">#37143</a>, <a href="https://redirect.github.com/facebook/react/pull/37241">#37241</a>) <ul> <li>Added an <code>onBrowserBailout</code> option to the <code>react-dom/server</code> APIs to observe when a subtree defers to the browser (<a href="https://github.com/gnoff"><code>@gnoff</code></a> <a href="https://redirect.github.com/facebook/react/pull/37193">#37193</a>)</li> </ul> </li> </ul> <h3>Notable changes</h3> <ul> <li>Enable Trusted Types API integration (<a href="https://github.com/rickhanlonii"><code>@rickhanlonii</code></a> <a href="https://redirect.github.com/facebook/react/pull/35816">#35816</a>)</li> <li>Transitions now render independently instead of being entangled into a single render, so a slow transition no longer holds up unrelated ones (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li> <li>Added a DEV-only warning when a component appears to have been unblocked by calling <code>use()</code> conditionally (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>: <a href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>, <a href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>, <a href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li> </ul> <h3>All Changes</h3> <h4>React</h4> <ul> <li>Fast Refresh Fixes <ul> <li>Fix Fast Refresh to find and remount edits to components wrapped behind <code>lazy()</code> (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36965">#36965</a>)</li> <li>Fix Fast Refresh so edits to a <code>memo()</code> comparison function take effect (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36964">#36964</a>)</li> <li>Fix Fast Refresh crash when an edit changes the kind of a component's type (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36963">#36963</a>)</li> <li>Unify hot reload type resolution for Fast Refresh (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36962">#36962</a>)</li> <li>Fix Fast Refresh to remount correctly when an edit changes the component kind (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36950">#36950</a>)</li> <li>Double invoke effects in StrictMode after Fast Refresh (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35962">#35962</a>)</li> </ul> </li> <li>Performance Track Fixes <ul> <li>Prevent crash when accessing <code>$$typeof</code> in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35679">#35679</a>)</li> <li>Handle non-string function names in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35659">#35659</a>)</li> <li>Use minus (<code>-</code>) instead of en dash for removed props in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35649">#35649</a>)</li> <li>Handle arrays with bigints in deep objects in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35648">#35648</a>)</li> <li>Don't enumerate typed array props in Performance Tracks in DEV (<a href="https://github.com/UditDewan"><code>@UditDewan</code></a> <a href="https://redirect.github.com/facebook/react/pull/36913">#36913</a>)</li> <li>Bail out of diffing wide objects and arrays in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/34742">#34742</a>)</li> <li>Clear potentially large performance measures in DEV (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a> <a href="https://redirect.github.com/facebook/react/pull/34803">#34803</a>)</li> <li>Fix missing else branch for renders with no props change in Performance Tracks (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a> <a href="https://redirect.github.com/facebook/react/pull/34837">#34837</a>)</li> </ul> </li> <li>Activity Fixes <ul> <li>Fix <code>useSyncExternalStore</code> missing store mutations that happened while an Activity tree was hidden (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36947">#36947</a>)</li> <li>Hide portal contents when an Activity is hidden (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/35091">#35091</a>)</li> <li>Prevent metadata hoisting in hidden <code><Activity></code> trees (<a href="https://github.com/ronnakamoto"><code>@ronnakamoto</code></a> <a href="https://redirect.github.com/facebook/react/pull/34983">#34983</a>)</li> <li>Prevent errors thrown inside a hidden Activity from escaping to the visible UI (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/35074">#35074</a>)</li> <li>Don't unhide a node if a direct parent Offscreen is still hidden (<a href="https://github.com/sebmarkbage"><code>@sebmarkbage</code></a> <a href="https://redirect.github.com/facebook/react/pull/34821">#34821</a>)</li> <li>Don't show internal <code><Offscreen></code> component in error messages (<a href="https://github.com/rickhanlonii"><code>@rickhanlonii</code></a> <a href="https://redirect.github.com/facebook/react/pull/35763">#35763</a>)</li> </ul> </li> <li>Warn in DEV when a component appears to have been unblocked by a conditional <code>use()</code> (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>: <a href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>, <a href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>, <a href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li> <li>Render transitions independently instead of entangling them into a single render (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li> <li>Fix hang when updating a dehydrated boundary inside a hidden tree (<a href="https://github.com/gaearon"><code>@gaearon</code></a> <a href="https://redirect.github.com/facebook/react/pull/37135">#37135</a>)</li> <li>Don't reacquire Host Singletons during dev effect validation (<a href="https://github.com/gnoff"><code>@gnoff</code></a> <a href="https://redirect.github.com/facebook/react/pull/37113">#37113</a>)</li> <li>Only remove properties from Host Singletons on release (<a href="https://github.com/gnoff"><code>@gnoff</code></a> <a href="https://redirect.github.com/facebook/react/pull/37112">#37112</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/react/react/commit/2dc7da790d6388b95b83198ca9b588b2ad5f5c0b"><code>2dc7da7</code></a> [test] Bump Jest to 30.4 (<a href="https://github.com/react/react/tree/HEAD/packages/react/issues/37382">#37382</a>)</li> <li><a href="https://github.com/react/react/commit/4f9389423b7319e1f7acc3d158c84a8365462748"><code>4f93894</code></a> docs: remove stale parentType param from validateChildKeys JSDoc (<a href="https://github.com/react/react/tree/HEAD/packages/react/issues/36928">#36928</a>)</li> <li><a href="https://github.com/react/react/commit/dbc37501ffeaf8fec45af5898caf1c3d64ad10bf"><code>dbc3750</code></a> Update required references to GitHub repo (<a href="https://github.com/react/react/tree/HEAD/packages/react/issues/36752">#36752</a>)</li> <li><a href="https://github.com/react/react/commit/900ae094d85b11c67d53dd14af50a2bda5db4495"><code>900ae09</code></a> [flow] Bump flow to v0.317.0 (<a href="https://github.com/react/react/tree/HEAD/packages/react/issues/36701">#36701</a>)</li> <li><a href="https://github.com/react/react/commit/fbb137059e4aacfaab1d36516e9b55050b4a0454"><code>fbb1370</code></a> [flow] Bump flow to v0.307.1 (<a href="https://github.com/react/react/tree/HEAD/packages/react/issues/36199">#36199</a>)</li> <li><a href="https://github.com/react/react/commit/56922cf751fab6c7ab4c12ddbbd15839959fa255"><code>56922cf</code></a> [react-native-renderer] Delete Paper (legacy) renderer (<a href="https://github.com/react/react/tree/HEAD/packages/react/issues/36285">#36285</a>)</li> <li><a href="https://github.com/react/react/commit/74568e8627aa43469b74f2972f427a209639d0b6"><code>74568e8</code></a> [Flight] Transport <code>AggregateErrors.errors</code> (<a href="https://github.com/react/react/tree/HEAD/packages/react/issues/36156">#36156</a>)</li> <li><a href="https://github.com/react/react/commit/e66ef6480ecd19c6885f2c06dec34fec1fdc0a98"><code>e66ef64</code></a> [tests] remove withoutStack from assertConsole helpers (<a href="https://github.com/react/react/tree/HEAD/packages/react/issues/35498">#35498</a>)</li> <li><a href="https://github.com/react/react/commit/db71391c5c70dc113560d1c23d0b6548604d827f"><code>db71391</code></a> [Fiber] Instrument the lazy initializer thenable in all cases (<a href="https://github.com/react/react/tree/HEAD/packages/react/issues/35521">#35521</a>)</li> <li><a href="https://github.com/react/react/commit/3e1abcc8d7083a13adf4774feb0d67ecbe4a2bc4"><code>3e1abcc</code></a> [tests] Require exact error messages in assertConsole helpers (<a href="https://github.com/react/react/tree/HEAD/packages/react/issues/35497">#35497</a>)</li> <li>Additional commits viewable in <a href="https://github.com/react/react/commits/v19.3.0/packages/react">compare view</a></li> </ul> </details> <br /> Updates `@types/react` from 19.2.18 to 19.3.0 <details> <summary>Commits</summary> <ul> <li>See full diff in <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react">compare view</a></li> </ul> </details> <br /> Updates `react-dom` from 19.2.8 to 19.3.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/react/react/releases">react-dom's releases</a>.</em></p> <blockquote> <h2>19.3.0 (September 9, 2026)</h2> <p>Below is a list of all new features, APIs, and bug fixes.</p> <p>Read the <a href="https://react.dev/blog/2026/09/09/react-19-3">React 19.3 release post</a> for more information.</p> <h2>New React Features</h2> <ul> <li><code><ViewTransition /></code>: Adds <code><ViewTransition /></code> and <code>addTransitionType</code> APIs to power View Transition animations in React (<a href="https://github.com/sebmarkbage"><code>@sebmarkbage</code></a>, <a href="https://github.com/jackpope"><code>@jackpope</code></a>, <a href="https://github.com/gaearon"><code>@gaearon</code></a>: <a href="https://redirect.github.com/facebook/react/pull/31975">#31975</a>, <a href="https://redirect.github.com/facebook/react/pull/31987">#31987</a>, <a href="https://redirect.github.com/facebook/react/pull/31996">#31996</a>, <a href="https://redirect.github.com/facebook/react/pull/31999">#31999</a>, <a href="https://redirect.github.com/facebook/react/pull/32001">#32001</a>, <a href="https://redirect.github.com/facebook/react/pull/32002">#32002</a>, <a href="https://redirect.github.com/facebook/react/pull/32028">#32028</a>, <a href="https://redirect.github.com/facebook/react/pull/32029">#32029</a>, <a href="https://redirect.github.com/facebook/react/pull/32031">#32031</a>, <a href="https://redirect.github.com/facebook/react/pull/32034">#32034</a>, <a href="https://redirect.github.com/facebook/react/pull/32038">#32038</a>, <a href="https://redirect.github.com/facebook/react/pull/32041">#32041</a>, <a href="https://redirect.github.com/facebook/react/pull/32050">#32050</a>, <a href="https://redirect.github.com/facebook/react/pull/32090">#32090</a>, <a href="https://redirect.github.com/facebook/react/pull/32105">#32105</a>, <a href="https://redirect.github.com/facebook/react/pull/32254">#32254</a>, <a href="https://redirect.github.com/facebook/react/pull/32379">#32379</a>, <a href="https://redirect.github.com/facebook/react/pull/32422">#32422</a>, <a href="https://redirect.github.com/facebook/react/pull/32462">#32462</a>, <a href="https://redirect.github.com/facebook/react/pull/32540">#32540</a>, <a href="https://redirect.github.com/facebook/react/pull/32545">#32545</a>, <a href="https://redirect.github.com/facebook/react/pull/32585">#32585</a>, <a href="https://redirect.github.com/facebook/react/pull/32599">#32599</a>, <a href="https://redirect.github.com/facebook/react/pull/32611">#32611</a>, <a href="https://redirect.github.com/facebook/react/pull/32612">#32612</a>, <a href="https://redirect.github.com/facebook/react/pull/32617">#32617</a>, <a href="https://redirect.github.com/facebook/react/pull/32651">#32651</a>, <a href="https://redirect.github.com/facebook/react/pull/32653">#32653</a>, <a href="https://redirect.github.com/facebook/react/pull/32656">#32656</a>, <a href="https://redirect.github.com/facebook/react/pull/32664">#32664</a>, <a href="https://redirect.github.com/facebook/react/pull/32699">#32699</a>, <a href="https://redirect.github.com/facebook/react/pull/32723">#32723</a>, <a href="https://redirect.github.com/facebook/react/pull/32734">#32734</a>, <a href="https://redirect.github.com/facebook/react/pull/32751">#32751</a>, <a href="https://redirect.github.com/facebook/react/pull/32752">#32752</a>, <a href="https://redirect.github.com/facebook/react/pull/32760">#32760</a>, <a href="https://redirect.github.com/facebook/react/pull/32761">#32761</a>, <a href="https://redirect.github.com/facebook/react/pull/32764">#32764</a>, <a href="https://redirect.github.com/facebook/react/pull/32772">#32772</a>, <a href="https://redirect.github.com/facebook/react/pull/32790">#32790</a>, <a href="https://redirect.github.com/facebook/react/pull/32819">#32819</a>, <a href="https://redirect.github.com/facebook/react/pull/32820">#32820</a>, <a href="https://redirect.github.com/facebook/react/pull/32822">#32822</a>, <a href="https://redirect.github.com/facebook/react/pull/32833">#32833</a>, <a href="https://redirect.github.com/facebook/react/pull/32849">#32849</a>, <a href="https://redirect.github.com/facebook/react/pull/33094">#33094</a>, <a href="https://redirect.github.com/facebook/react/pull/33191">#33191</a>, <a href="https://redirect.github.com/facebook/react/pull/33200">#33200</a>, <a href="https://redirect.github.com/facebook/react/pull/33206">#33206</a>, <a href="https://redirect.github.com/facebook/react/pull/33293">#33293</a>, <a href="https://redirect.github.com/facebook/react/pull/33330">#33330</a>, <a href="https://redirect.github.com/facebook/react/pull/33331">#33331</a>, <a href="https://redirect.github.com/facebook/react/pull/33332">#33332</a>, <a href="https://redirect.github.com/facebook/react/pull/33357">#33357</a>, <a href="https://redirect.github.com/facebook/react/pull/33362">#33362</a>, <a href="https://redirect.github.com/facebook/react/pull/33433">#33433</a>, <a href="https://redirect.github.com/facebook/react/pull/33576">#33576</a>, <a href="https://redirect.github.com/facebook/react/pull/34374">#34374</a>, <a href="https://redirect.github.com/facebook/react/pull/34450">#34450</a>, <a href="https://redirect.github.com/facebook/react/pull/34481">#34481</a>, <a href="https://redirect.github.com/facebook/react/pull/34500">#34500</a>, <a href="https://redirect.github.com/facebook/react/pull/34502">#34502</a>, <a href="https://redirect.github.com/facebook/react/pull/34510">#34510</a>, <a href="https://redirect.github.com/facebook/react/pull/34511">#34511</a>, <a href="https://redirect.github.com/facebook/react/pull/34539">#34539</a>, <a href="https://redirect.github.com/facebook/react/pull/35567">#35567</a>, <a href="https://redirect.github.com/facebook/react/pull/35564">#35564</a>, <a href="https://redirect.github.com/facebook/react/pull/35485">#35485</a>, <a href="https://redirect.github.com/facebook/react/pull/35380">#35380</a>, <a href="https://redirect.github.com/facebook/react/pull/35063">#35063</a>, <a href="https://redirect.github.com/facebook/react/pull/35060">#35060</a>, <a href="https://redirect.github.com/facebook/react/pull/34676">#34676</a>, <a href="https://redirect.github.com/facebook/react/pull/36917">#36917</a>, <a href="https://redirect.github.com/facebook/react/pull/35337">#35337</a>, <a href="https://redirect.github.com/facebook/react/pull/35520">#35520</a>)</li> <li>Fragment Refs: Add Refs to <code><Fragment /></code> to support composable platform behavior (<a href="https://github.com/jackpope"><code>@jackpope</code></a>, <a href="https://github.com/sebmarkbage"><code>@sebmarkbage</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>, <a href="https://github.com/Dhakshin2007"><code>@Dhakshin2007</code></a>, <a href="https://github.com/chirokas"><code>@chirokas</code></a>, <a href="https://github.com/teamleaderleo"><code>@teamleaderleo</code></a>, <a href="https://github.com/fallintoplace"><code>@fallintoplace</code></a>: <a href="https://redirect.github.com/facebook/react/pull/32465">#32465</a>, <a href="https://redirect.github.com/facebook/react/pull/32613">#32613</a>, <a href="https://redirect.github.com/facebook/react/pull/32619">#32619</a>, <a href="https://redirect.github.com/facebook/react/pull/32654">#32654</a>, <a href="https://redirect.github.com/facebook/react/pull/32660">#32660</a>, <a href="https://redirect.github.com/facebook/react/pull/32682">#32682</a>, <a href="https://redirect.github.com/facebook/react/pull/32722">#32722</a>, <a href="https://redirect.github.com/facebook/react/pull/32813">#32813</a>, <a href="https://redirect.github.com/facebook/react/pull/32814">#32814</a>, <a href="https://redirect.github.com/facebook/react/pull/33056">#33056</a>, <a href="https://redirect.github.com/facebook/react/pull/33058">#33058</a>, <a href="https://redirect.github.com/facebook/react/pull/33093">#33093</a>, <a href="https://redirect.github.com/facebook/react/pull/34069">#34069</a>, <a href="https://redirect.github.com/facebook/react/pull/34103">#34103</a>, <a href="https://redirect.github.com/facebook/react/pull/34544">#34544</a>, <a href="https://redirect.github.com/facebook/react/pull/34545">#34545</a>, <a href="https://redirect.github.com/facebook/react/pull/37062">#37062</a>, <a href="https://redirect.github.com/facebook/react/pull/37061">#37061</a>, <a href="https://redirect.github.com/facebook/react/pull/37060">#37060</a>, <a href="https://redirect.github.com/facebook/react/pull/36047">#36047</a>, <a href="https://redirect.github.com/facebook/react/pull/36010">#36010</a>, <a href="https://redirect.github.com/facebook/react/pull/35642">#35642</a>, <a href="https://redirect.github.com/facebook/react/pull/35641">#35641</a>, <a href="https://redirect.github.com/facebook/react/pull/35637">#35637</a>, <a href="https://redirect.github.com/facebook/react/pull/35630">#35630</a>, <a href="https://redirect.github.com/facebook/react/pull/34935">#34935</a>, <a href="https://redirect.github.com/facebook/react/pull/37457">#37457</a>, <a href="https://redirect.github.com/facebook/react/pull/37408">#37408</a>, <a href="https://redirect.github.com/facebook/react/pull/37326">#37326</a>, <a href="https://redirect.github.com/facebook/react/pull/37251">#37251</a>, <a href="https://redirect.github.com/facebook/react/pull/37171">#37171</a>, <a href="https://redirect.github.com/facebook/react/pull/37169">#37169</a>, <a href="https://redirect.github.com/facebook/react/pull/37168">#37168</a>, <a href="https://redirect.github.com/facebook/react/pull/37167">#37167</a>, <a href="https://redirect.github.com/facebook/react/pull/37166">#37166</a>, <a href="https://redirect.github.com/facebook/react/pull/37165">#37165</a>, <a href="https://redirect.github.com/facebook/react/pull/37164">#37164</a>, <a href="https://redirect.github.com/facebook/react/pull/37163">#37163</a>, <a href="https://redirect.github.com/facebook/react/pull/37162">#37162</a>, <a href="https://redirect.github.com/facebook/react/pull/37161">#37161</a>, <a href="https://redirect.github.com/facebook/react/pull/37160">#37160</a>, <a href="https://redirect.github.com/facebook/react/pull/37125">#37125</a>, <a href="https://redirect.github.com/facebook/react/pull/37063">#37063</a>)</li> </ul> <h2>New React DOM Features</h2> <ul> <li><code>browser()</code>: a new <code>react-dom</code> API that returns a usable which errors during server rendering and resolves in the browser. <code>use(browser())</code> inside a <code><Suspense></code> boundary marks a subtree as browser-only without reporting a recoverable error (<a href="https://github.com/gnoff"><code>@gnoff</code></a>: <a href="https://redirect.github.com/facebook/react/pull/37143">#37143</a>, <a href="https://redirect.github.com/facebook/react/pull/37241">#37241</a>) <ul> <li>Added an <code>onBrowserBailout</code> option to the <code>react-dom/server</code> APIs to observe when a subtree defers to the browser (<a href="https://github.com/gnoff"><code>@gnoff</code></a> <a href="https://redirect.github.com/facebook/react/pull/37193">#37193</a>)</li> </ul> </li> </ul> <h2>Notable changes</h2> <ul> <li>Enable Trusted Types API integration (<a href="https://github.com/rickhanlonii"><code>@rickhanlonii</code></a> <a href="https://redirect.github.com/facebook/react/pull/35816">#35816</a>)</li> <li>Transitions now render independently instead of being entangled into a single render, so a slow transition no longer holds up unrelated ones (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li> <li>Added a DEV-only warning when a component appears to have been unblocked by calling <code>use()</code> conditionally (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>: <a href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>, <a href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>, <a href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li> </ul> <h2>All Changes</h2> <h3>React</h3> <ul> <li>Fast Refresh Fixes <ul> <li>Fix Fast Refresh to find and remount edits to components wrapped behind <code>lazy()</code> (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36965">#36965</a>)</li> <li>Fix Fast Refresh so edits to a <code>memo()</code> comparison function take effect (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36964">#36964</a>)</li> <li>Fix Fast Refresh crash when an edit changes the kind of a component's type (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36963">#36963</a>)</li> <li>Unify hot reload type resolution for Fast Refresh (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36962">#36962</a>)</li> <li>Fix Fast Refresh to remount correctly when an edit changes the component kind (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36950">#36950</a>)</li> <li>Double invoke effects in StrictMode after Fast Refresh (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35962">#35962</a>)</li> </ul> </li> <li>Performance Track Fixes <ul> <li>Prevent crash when accessing <code>$$typeof</code> in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35679">#35679</a>)</li> <li>Handle non-string function names in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35659">#35659</a>)</li> <li>Use minus (<code>-</code>) instead of en dash for removed props in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35649">#35649</a>)</li> <li>Handle arrays with bigints in deep objects in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/35648">#35648</a>)</li> <li>Don't enumerate typed array props in Performance Tracks in DEV (<a href="https://github.com/UditDewan"><code>@UditDewan</code></a> <a href="https://redirect.github.com/facebook/react/pull/36913">#36913</a>)</li> <li>Bail out of diffing wide objects and arrays in Performance Tracks (<a href="https://github.com/eps1lon"><code>@eps1lon</code></a> <a href="https://redirect.github.com/facebook/react/pull/34742">#34742</a>)</li> <li>Clear potentially large performance measures in DEV (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a> <a href="https://redirect.github.com/facebook/react/pull/34803">#34803</a>)</li> <li>Fix missing else branch for renders with no props change in Performance Tracks (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a> <a href="https://redirect.github.com/facebook/react/pull/34837">#34837</a>)</li> </ul> </li> <li>Activity Fixes <ul> <li>Fix <code>useSyncExternalStore</code> missing store mutations that happened while an Activity tree was hidden (<a href="https://github.com/sophiebits"><code>@sophiebits</code></a> <a href="https://redirect.github.com/facebook/react/pull/36947">#36947</a>)</li> <li>Hide portal contents when an Activity is hidden (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/35091">#35091</a>)</li> <li>Prevent metadata hoisting in hidden <code><Activity></code> trees (<a href="https://github.com/ronnakamoto"><code>@ronnakamoto</code></a> <a href="https://redirect.github.com/facebook/react/pull/34983">#34983</a>)</li> <li>Prevent errors thrown inside a hidden Activity from escaping to the visible UI (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/35074">#35074</a>)</li> <li>Don't unhide a node if a direct parent Offscreen is still hidden (<a href="https://github.com/sebmarkbage"><code>@sebmarkbage</code></a> <a href="https://redirect.github.com/facebook/react/pull/34821">#34821</a>)</li> <li>Don't show internal <code><Offscreen></code> component in error messages (<a href="https://github.com/rickhanlonii"><code>@rickhanlonii</code></a> <a href="https://redirect.github.com/facebook/react/pull/35763">#35763</a>)</li> </ul> </li> <li>Warn in DEV when a component appears to have been unblocked by a conditional <code>use()</code> (<a href="https://github.com/hoxyq"><code>@hoxyq</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>: <a href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>, <a href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>, <a href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li> <li>Render transitions independently instead of entangling them into a single render (<a href="https://github.com/acdlite"><code>@acdlite</code></a> <a href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/react/react/blob/main/CHANGELOG.md">react-dom's changelog</a>.</em></p> <blockquote> <h2>19.3.0 (September 9, 2026)</h2> <h3>New React Features</h3> <ul> <li><code><ViewTransition /></code>: Adds <code><ViewTransition /></code> and <code>addTransitionType</code> APIs to power View Transition animations in React (<a href="https://github.com/sebmarkbage"><code>@sebmarkbage</code></a>, <a href="https://github.com/jackpope"><code>@jackpope</code></a>, <a href="https://github.com/gaearon"><code>@gaearon</code></a>: <a href="https://redirect.github.com/facebook/react/pull/31975">#31975</a>, <a href="https://redirect.github.com/facebook/react/pull/31987">#31987</a>, <a href="https://redirect.github.com/facebook/react/pull/31996">#31996</a>, <a href="https://redirect.github.com/facebook/react/pull/31999">#31999</a>, <a href="https://redirect.github.com/facebook/react/pull/32001">#32001</a>, <a href="https://redirect.github.com/facebook/react/pull/32002">#32002</a>, <a href="https://redirect.github.com/facebook/react/pull/32028">#32028</a>, <a href="https://redirect.github.com/facebook/react/pull/32029">#32029</a>, <a href="https://redirect.github.com/facebook/react/pull/32031">#32031</a>, <a href="https://redirect.github.com/facebook/react/pull/32034">#32034</a>, <a href="https://redirect.github.com/facebook/react/pull/32038">#32038</a>, <a href="https://redirect.github.com/facebook/react/pull/32041">#32041</a>, <a href="https://redirect.github.com/facebook/react/pull/32050">#32050</a>, <a href="https://redirect.github.com/facebook/react/pull/32090">#32090</a>, <a href="https://redirect.github.com/facebook/react/pull/32105">#32105</a>, <a href="https://redirect.github.com/facebook/react/pull/32254">#32254</a>, <a href="https://redirect.github.com/facebook/react/pull/32379">#32379</a>, <a href="https://redirect.github.com/facebook/react/pull/32422">#32422</a>, <a href="https://redirect.github.com/facebook/react/pull/32462">#32462</a>, <a href="https://redirect.github.com/facebook/react/pull/32540">#32540</a>, <a href="https://redirect.github.com/facebook/react/pull/32545">#32545</a>, <a href="https://redirect.github.com/facebook/react/pull/32585">#32585</a>, <a href="https://redirect.github.com/facebook/react/pull/32599">#32599</a>, <a href="https://redirect.github.com/facebook/react/pull/32611">#32611</a>, <a href="https://redirect.github.com/facebook/react/pull/32612">#32612</a>, <a href="https://redirect.github.com/facebook/react/pull/32617">#32617</a>, <a href="https://redirect.github.com/facebook/react/pull/32651">#32651</a>, <a href="https://redirect.github.com/facebook/react/pull/32653">#32653</a>, <a href="https://redirect.github.com/facebook/react/pull/32656">#32656</a>, <a href="https://redirect.github.com/facebook/react/pull/32664">#32664</a>, <a href="https://redirect.github.com/facebook/react/pull/32699">#32699</a>, <a href="https://redirect.github.com/facebook/react/pull/32723">#32723</a>, <a href="https://redirect.github.com/facebook/react/pull/32734">#32734</a>, <a href="https://redirect.github.com/facebook/react/pull/32751">#32751</a>, <a href="https://redirect.github.com/facebook/react/pull/32752">#32752</a>, <a href="https://redirect.github.com/facebook/react/pull/32760">#32760</a>, <a href="https://redirect.github.com/facebook/react/pull/32761">#32761</a>, <a href="https://redirect.github.com/facebook/react/pull/32764">#32764</a>, <a href="https://redirect.github.com/facebook/react/pull/32772">#32772</a>, <a href="https://redirect.github.com/facebook/react/pull/32790">#32790</a>, <a href="https://redirect.github.com/facebook/react/pull/32819">#32819</a>, <a href="https://redirect.github.com/facebook/react/pull/32820">#32820</a>, <a href="https://redirect.github.com/facebook/react/pull/32822">#32822</a>, <a href="https://redirect.github.com/facebook/react/pull/32833">#32833</a>, <a href="https://redirect.github.com/facebook/react/pull/32849">#32849</a>, <a href="https://redirect.github.com/facebook/react/pull/33094">#33094</a>, <a href="https://redirect.github.com/facebook/react/pull/33191">#33191</a>, <a href="https://redirect.github.com/facebook/react/pull/33200">#33200</a>, <a href="https://redirect.github.com/facebook/react/pull/33206">#33206</a>, <a href="https://redirect.github.com/facebook/react/pull/33293">#33293</a>, <a href="https://redirect.github.com/facebook/react/pull/33330">#33330</a>, <a href="https://redirect.github.com/facebook/react/pull/33331">#33331</a>, <a href="https://redirect.github.com/facebook/react/pull/33332">#33332</a>, <a href="https://redirect.github.com/facebook/react/pull/33357">#33357</a>, <a href="https://redirect.github.com/facebook/react/pull/33362">#33362</a>, <a href="https://redirect.github.com/facebook/react/pull/33433">#33433</a>, <a href="https://redirect.github.com/facebook/react/pull/33576">#33576</a>, <a href="https://redirect.github.com/facebook/react/pull/34374">#34374</a>, <a href="https://redirect.github.com/facebook/react/pull/34450">#34450</a>, <a href="https://redirect.github.com/facebook/react/pull/34481">#34481</a>, <a href="https://redirect.github.com/facebook/react/pull/34500">#34500</a>, <a href="https://redirect.github.com/facebook/react/pull/34502">#34502</a>, <a href="https://redirect.github.com/facebook/react/pull/34510">#34510</a>, <a href="https://redirect.github.com/facebook/react/pull/34511">#34511</a>, <a href="https://redirect.github.com/facebook/react/pull/34539">#34539</a>, <a href="https://redirect.github.com/facebook/react/pull/35567">#35567</a>, <a href="https://redirect.github.com/facebook/react/pull/35564">#35564</a>, <a href="https://redirect.github.com/facebook/react/pull/35485">#35485</a>, <a href="https://redirect.github.com/facebook/react/pull/35380">#35380</a>, <a href="https://redirect.github.com/facebook/react/pull/35063">#35063</a>, <a href="https://redirect.github.com/facebook/react/pull/35060">#35060</a>, <a href="https://redirect.github.com/facebook/react/pull/34676">#34676</a>, <a href="https://redirect.github.com/facebook/react/pull/36917">#36917</a>, <a href="https://redirect.github.com/facebook/react/pull/35337">#35337</a>, <a href="https://redirect.github.com/facebook/react/pull/35520">#35520</a>)</li> <li>Fragment Refs: Add Refs to <code><Fragment /></code> to support composable platform behavior (<a href="https://github.com/jackpope"><code>@jackpope</code></a>, <a href="https://github.com/sebmarkbage"><code>@sebmarkbage</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>, <a href="https://github.com/Dhakshin2007"><code>@Dhakshin2007</code></a>, <a href="https://github.com/chirokas"><code>@chirokas</code></a>, <a href="https://github.com/teamleaderleo"><code>@teamleaderleo</code></a>, <a href="https://github.com/fallintoplace"><code>@fallintoplace</code></a>: <a href="https://redirect.github.com/facebook/react/pull/32465">#32465</a>, <a href="https://redirect.github.com/facebook/react/pull/32613">#32613</a>, <a href="https://redirect.github.com/facebook/react/pull/32619">#32619</a>, <a href="https://redirect.github.com/facebook/react/pull/32654">#32654</a>, <a href="https://redirect.github.com/facebook/react/pull/32660">#32660</a>, <a href="https://redirect.github.com/facebook/react/pull/32682">#32682</a>, <a href="https://redirect.github.com/facebook/react/pull/32722">#32722</a>, <a href="https://redirect.github.com/facebook/react/pull/32813">#32813</a>, <a href="https://redirect.github.com/facebook/react/pull/32814">#32814</a>, <a href="https://redirect.github.com/facebook/react/pull/33056">#33056</a>, <a href="https://redirect.github.com/facebook/react/pull/33058">#33058</a>, <a href="https://redirect.github.com/facebook/react/pull/33093">#33093</a>, <a href="https://redirect.github.com/facebook/react/pull/34069">#34069</a>, <a href="https://redirect.github.com/facebook/react/pull/34103">#34103</a>, <a href="https://redirect.github.com/facebook/react/pull/34544">#34544</a>, <a href="https://redirect.github.com/facebook/react/pull/34545">#34545</a>, <a href="https://redirect.github.com/facebook/react/pull/37062">#37062</a>, <a href="https://redirect.github.com/fac…
## Problem In light mode with `studio-modern`, album titles in a subpage's cover grid are nearly unreadable. For example, "An der Ostsee" on `/deutschland` shows as dark text on a dark grey-green bar. ## Cause `SubpageGridView` sets the cover's dominant colour (the loading placeholder) as an inline `background-color` on the whole card. `studio-modern` adds a caption bar below the image and gives the card `background: var(--bg-card)`, but the inline style wins. So the caption bar takes the photo's colour. The caption title uses `--text-primary`, which is dark in light mode, so it ends up dark on dark. In dark mode it happens to stay readable, because most dominant colours are dark. This dates back to March and has nothing to do with the recent contrast work. ## Fix The inline colour moves from the card to `.subpage-grid__item-media`, the image box, where it serves its purpose as a placeholder behind the loading cover. Every other preset fills the card with the media box (`height: 100%`) and hides the caption, so nothing changes visually there. ## Checked - `npx tsc --noEmit`: 0 errors - ESLint and Prettier: clean on the changed file Co-authored-by: ralksta <ralksta@users.noreply.github.com>
…#580) ## Why Follow-up to #578. I ran an axe `color-contrast` audit in a real browser across all seven presets in light mode, on `/`, a subpage, an album, `/about`, `/map` and `/impressum`. It found **129 failures**, all traceable to four spots. In three of them, an `opacity` sits on top of a colour token that #571 had already tuned to exactly 4.5:1. | Where | Presets | Before | |---|---|---| | Footer "Impressum" link: `opacity: 0.5` on `--text-muted` | all except studio-modern | ~2:1 | | `/impressum` subtitle, section titles, source note: `opacity` 0.6/0.8 | all | 2.7–3.2:1 | | Editorial hero subtitle: `opacity: 0.7` | editorial | 2.95:1 | | Nav links: `--text-muted` `#64748b` on the tinted header | minimal, monograph | 4.39:1 | ## What - Opacity dimming replaced with the colour tokens (`--text-muted`, `--text-secondary`). - The footer link's hover now changes colour rather than opacity. - Light `--text-muted` for minimal and monograph goes from `#64748b` to `#627083`: 4.65:1 on the header, 5.04:1 on white. ## Verified The same audit with these rules applied: - **Light mode:** 129 → **0** failures. - **Dark mode:** 56 → 12. The remaining 12 are the configured accent `#e60012` on studio-modern's small index numbers (3.7–3.9:1). That's a design choice about the accent, so it's left alone here. Prettier is clean. CSS only, no TypeScript touched. ## Also: `npm run audit:contrast` The audit is now in the repo as `scripts/contrast-audit.mjs`. It discovers the pages from the running site, checks all presets in light and dark mode, blocks analytics requests and exits 1 on any failure: ``` npm run audit:contrast -- http://localhost:7211 ``` `axe-core` was only a transitive dependency (via `eslint-plugin-jsx-a11y`), so it is now a direct devDependency. `pnpm-lock.yaml` gains only the 3-line importer entry, and `pnpm install --frozen-lockfile` accepts it. --------- Co-authored-by: ralksta <ralksta@users.noreply.github.com>
## What - **Diagnostics moves from a modal to `/admin/diagnostics`.** Findings that want a look come first. The passed checks follow, grouped into connection, security and content. Overview tiles show Immich, config, backups and version. Backups and cache clearing sit on the page instead of in two further places. **Copy report** now includes the version. - **New alt-text report.** Folio's alt text is the Immich description, and it is used only while *Photo Description* (the `caption` EXIF group) is on. A photo without a description rendered with an empty `alt`, and nothing said so. `GET /api/admin/alt-text` lists the published photos without one, by album, with thumbnails linking to the photo in Immich. If captions are switched off, the page says that instead and links to the setting. - **Every finding that wants a look links to its fix** instead of showing a severity pill that looked like a button. Where the fix lives in the admin (passwords, pages), the button goes there. For an environment variable or the volume, it links to the matching docs section. The dot carries the severity, and a screen-reader label repeats it. - **A finding about a single album opens the page it is published on.** It goes to `/admin/pages?album=<id>`, which opens that subpage's sheet and briefly marks the album's tile. A standalone album has no page, so its own drawer opens. The doctor findings `albums-shared` and `album-ids` now carry `albumIds` for this. The parameter is removed once read. - **The alt-text photo list starts collapsed.** With hundreds of gaps, it pushed every other check below the fold. - The status-badge dropdown and the Help page link to the page. `DoctorModal` is removed. - After each run, the page hands the doctor level to the badge through a window event, so the badge updates without asking Immich again. - **New `--admin-warning` token.** `studio-modern` recolours `--admin-accent` red, which made every warning read as an error. `.status-val.update` already referenced the token without it being defined. ## Decisions worth a second look - **Alt text is kept out of the status badge.** Most libraries have photos without a description, and a badge that never turns green stops being read. It is counted in the page's headline. - **Immich links use Immich's own External domain** (`GET /server/config`) when it is set. That is the address Immich uses for its share links, so a browser can open it. Otherwise they fall back to the API URL minus `/api`, which is often an internal Docker name, and the page says where to set the domain. - **The report goes through `immich.getAlbum()`**, so it shares the album cache with the public site. An album that fails to load is counted, not fatal. ## Testing - `npx tsc --noEmit`, `npm run lint` (no new warnings), `npm run test:unit` (894 passed), `npm run format:check` and `npm run build` are all clean. - New `lib/__tests__/alt-text.test.ts` and `app/admin/__tests__/findAlbumAddress.test.ts`. `admin-guards.test.ts` has a row for the new route. - Checked against a live Immich: 15 albums, 584 published photos, 1 with a description. Screenshots were taken in dark, light and mobile widths. With an unreachable Immich URL, all albums are reported as unreadable and the page still renders. --------- Co-authored-by: ralksta <ralksta@users.noreply.github.com>
#581) ## Problem Deleting a journal entry was a bare `fs.unlink` (`lib/admin/journal-service.ts`), while saving rotated a backup. The one irreversible action in the journal was the one with no way back. On top of that, the Backup Manager only listed `gallery.yaml` and `settings.yaml`. Saves did write journal and `about.md` backups, but nothing in the panel could restore them, even though `docs/admin-panel.md` said it could. ## Changes - **Delete:** `deleteJournalEntry` now writes `<slug>.md.<ts>.deleted.bak` before unlinking. If that copy can't be written, the delete is refused. - **Restore:** new `restoreJournalBackup` / `listJournalBackups`. A restore snapshots the current version first (`.pre-restore.bak`), and a deleted entry comes back under its old slug. The name is validated by an anchored regex over the `isValidSlug` alphabet, plus a `basename` check and `containedPath`. - **Rotation:** `.deleted.bak` and `.pre-restore.bak` are exempt from the 10-backup limit, the same way `yaml-service` treats its pre-restore files. Re-using a slug can therefore never push out the deleted copy. - **about.md:** `restoreBackup` accepts `about.md` backups. The about route already writes them into `content/.backups/`. - **API:** `GET /api/admin/backups` also returns `about` and `journal`. `POST` takes an optional `target`, and `'journal'` routes to the journal service, because `<slug>.md.<ts>.bak` can't be told apart from an `about.md` backup by name alone. - **Backup Manager:** four tabs (Gallery / Settings / About / Journal). Journal rows show the slug and a "Deleted entry" badge. - **Journal studio:** the delete prompt says a copy is kept and where to restore it. - **Docs:** `docs/admin-panel.md` updated. The subpage `confirm()` in the page builder is left alone on purpose. That removal only changes local state and can be undone until Save. ## Testing - New `lib/__tests__/journal-backup.test.ts`, run against a real temp directory: delete keeps a copy; a failed backup blocks the delete; a deleted entry is restored; restore snapshots the current version; traversal names are rejected; rotation keeps the deleted snapshot and doesn't touch slugs that share a prefix. - `backup-restore.test.ts`: `about.md` is restored onto `content/about.md`; `about.yaml.*` and journal names are rejected. - `npx tsc --noEmit` clean, `npm run test:unit` 901/901, `npm run build` passes, eslint 0 errors. - Not clicked through in a browser. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
…#583) ## Problem The Live Preview tab in the subpage drawer drew its own banner and tiles in admin styling: a dashed box as a hero, a fixed `auto-fill` grid, and white cards with the name underneath. It knew nothing of the theme preset, the cover grid columns (`coverGrid` / global `grid.columns`), the caption bar, or the "03 — Collection" kicker. It showed a page the site never renders, which makes it worthless as a check before saving. ## Changes - **Real markup:** `SubpagePreview` now emits the markup and classes of `SubpageGridView`: `subpage-container`, `subpage-header`, the TOC, sections, and `subpage-grid__item` with its overlay and caption. The admin `<html>` already carries `data-preset` and the theme variables, so the preset CSS styles the preview exactly like the public page. There is no second stylesheet to keep in sync. - **Same grid and strings:** the cover grid is sized by the same `buildCoverGridVars()` from the subpage's `coverGrid` and the global column count. That count, and `lang` for the visitor dictionary, are read from `/api/admin/settings` each time the preview opens. - **Scaled canvas:** the page is laid out at 1280px and scaled into the drawer with `zoom`, so a three-column grid still reads as one. The media queries see a desktop viewport too. The canvas is `inert`: a picture of the page, not a second set of links. - **Kicker:** it counts enabled subpages only, as the page does. - **Class collision:** the page builder's own subpage list used the public `.subpage-grid` class and overrode its columns in `page-builder.css`. That override would have leaked into the preview, and the public grid rules were already leaking into the admin list. The list class is now `.subpage-tiles`. Unsaved drawer edits (title, subtitle, sections, albums, cover grid) show up live. A preset changed in Settings shows up once it is saved, since the preset comes from `<html data-preset>`. The note above the preview says so. ## Testing - Playwright against a dev server with a copy of `content/`, preset `studio-modern`: the preview shows the kicker "03 — Sammlung" (`lang: de`, a disabled subpage correctly skipped), the uppercase title, the meta line, three 381px columns and the numbered caption bar. The page builder's subpage list looks unchanged after the class rename. - Immich was not reachable from that server, so covers and photo counts were empty. Not checked with live thumbnails. - `npx tsc --noEmit` clean, `npm run test:unit` 884/884, `npm run build` passes, eslint 0 errors. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
…ll (#582) ## Problem Each settings section is its own route (`/admin/settings/[section]`), and the section page rendered the editor, so the editor remounted on every switch. **Changing the site title under General and then opening another section threw the edit away.** There was no warning: the save bar just disappeared, and settings.yaml was read again. I reproduced this in the browser on `dev` (edit the title → About → General: the field is back to its old value). There were two more problems: - **Two save bars.** "Save Settings" and "Save About" were never visible together, so an About edit could not be seen from the other sections, and the reverse. - **No leave-page warning for About.** `useUnsavedGuard` only watched `settings.yaml`, so closing the tab after editing About lost the edit without asking. ## Changes - **Editor in the layout:** it moves into the new `app/admin/settings/layout.tsx`. A layout stays mounted across its child routes, so edits survive a section switch. The section is read from the URL with `useParams()`, and the two pages now render `null`. - **One save bar:** a single "Save Changes" bar on every section, active while either `settings.yaml` or `about.md` has unsaved changes. It saves both. ⌘S/Ctrl+S and the leave-page warning cover both files too. If either save fails, the error takes priority in the bar. - **About loads once:** `about.md` is loaded the first time its section opens, and not again. Otherwise coming back to About would overwrite unsaved About edits with the file on disk. ## Testing - Playwright against a dev server with a copy of `content/`: edit the title → About (bar stays) → edit the name → General (title kept) → About (name kept) → ⌘S. Both `settings.yaml` and `about.md` were written, each with a backup. - `npx tsc --noEmit` clean, `npm run test:unit` 884/884, `npm run build` passes, eslint 0 errors. Came out of a UX review of the admin panel. The panel is used from one screen at a time, so none of this is visible until an edit goes missing. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Writes the `[Unreleased]` section for v0.15.0, covering the 18 commits on `dev` since v0.14.0. The release commit renames the heading to `[0.15.0] — <date>`, as it did for v0.14.0 (#553). The three open refactors in the milestone (#554–#556) moved to **v0.16.0**: all three are pure refactors with nothing user-visible, kept out of release prep on purpose. ## What is in it - **Security:** EXIF/GPS stripped from full-size originals (#558); Next.js 16.3.1 → 16.3.5 for GHSA-p293-qw3h-jr36, plus the js-yaml advisory (#571, #566). - **Added:** `/admin/diagnostics` with the alt-text report (#579); Markdown links in quote attributions (#563); journal and About restore in the Backup Manager, deleted entries included (#581). - **Fixed:** settings edits lost on a section switch (#582); Live Preview renders the real page (#583); EXIF orientation (#565/#572); WCAG contrast and keyboard access (#571, #580); light-mode surfaces (#562); proofing bar with transitions on (#560); studio-modern caption bar (#578). - **Upgrade notes:** bump `IMAGE_CACHE_VERSION` on deploy. Without that bump, browsers that already cached an original keep its GPS metadata for up to a year, which is the point #558 made about its own rollout. External contributors are credited inline: @lancetm714 (#560, #562, #563) and @ImScheinox (#572, which is also why #576 was reverted in #577). ## Checked - Every entry against its PR description and the code on `dev`. The version numbers come from the `package.json` diff `v0.14.0..origin/dev`. - The explicit CI token (`3ef9184`) is **not** listed. It already shipped in the v0.14.0 tag. - `npx prettier --check CHANGELOG.md` is clean. ## README The Contributors section gains **@ImScheinox** (first contribution, #572) and extends **@lancetm714**'s entry with the v0.15.0 work (#560, #562, #563). The changelog already credits both inline; the README is where the lasting thanks lives. --------- Co-authored-by: ralksta <ralksta@users.noreply.github.com>
main is a chain of one squash commit per release, each tree-identical to the dev commit it was made from. Because a squash leaves no parent link, the merge base of dev and main stays at the previous release, so every release PR re-presents the whole of the last release as a conflict. This merge takes dev's tree unchanged, which loses nothing: main (v0.14.0, e99d3f2) is tree-identical to 3ef9184, an ancestor of dev. What it does is move the merge base to main, so the v0.15.0 release PR applies cleanly.
CodeQL blocks the v0.15.0 release PR (#585) with two **high** alerts, *Uncontrolled data used in path expression*, both on `lib/admin/journal-service.ts:49`. That is `snapshotEntry`, added in #581, which built the backup path with `path.join`. The two sources are the slug from `DELETE /api/admin/journal/[slug]` and `backupFilename` from `POST /api/admin/backups`. ## Exploitable? No. The name is always `<slug>.md`, and the slug has already passed `isValidSlug` (delete, save) or the anchored `JOURNAL_BACKUP_NAME` regex plus `isValidSlug` (restore). Neither admits a dot or a separator. But that validation lives in the callers and in another module, and CodeQL does not follow it there. That is exactly why this file has `containedPath()`, as its own comment explains. ## Fix The backup path is built through `containedPath(BACKUP_DIR, …)` like every other path in the file. A path that would escape `.backups/` throws, and a delete is refused rather than carried out unprotected, because `deleteJournalEntry` snapshots before it unlinks. ## Checked `npx tsc --noEmit` is clean, `npm run test:unit` passes 918/918 (`journal-backup.test.ts` included), and eslint reports 0 errors. Whether the alerts clear shows up in this PR's own CodeQL run. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Closes #612. ## The decision `jsdom` and `@testing-library/react` come in as **dev** dependencies. The seven runtime dependencies from #480 are untouched — test tooling never reaches a user's browser or server, so that rule was never the thing standing in the way. ## What is set up - **Node stays the default environment.** Most of what is worth testing here is plain logic and needs no DOM, and the existing 918 tests should not get slower. - **A component test opts in per file** with `// @vitest-environment jsdom` as its first line. Explicit, visible in the file itself, and stable across vitest versions. - `include` now covers `.tsx` and adds `components/__tests__/`. - `CONTRIBUTING.md` documents the convention — including that **extracting the logic is still preferred over rendering it**. A component test is for behaviour that *is* the rendering: a state that hides a form, a control that must stay disabled, a shortcut that must still fire. ## Why SaveBar as the example It is small enough to read in one go, and it earns the coverage on its own: `SaveBar` decides whether an outcome reads as success or failure by testing whether the message happens to **start with the word "Error"**. The suite pins that as it behaves today — including the case where a genuine failure ("Could not reach the server") is styled as a success. That is exactly the argument #600 makes for a typed state, and it is now a verifiable one rather than an assertion in an issue. Seven tests: nothing rendered when there is nothing to act on, the unsaved badge, the button disabled unless dirty, disabled again while saving, `onSave` firing, the prefix-based message styling, and the preview link appearing only where it belongs. ## Verification - full suite: **925 tests in 69 files, all passing** (918 before, plus these 7) - `npx tsc --noEmit` clean, `npx eslint app components lib` clean - `npm run build` succeeds ## Two notes **This unblocks the refactors.** #608, #610, #611, #554 and #555 can now leave behind more tested surface than they found, instead of redistributing an untested one. The #619 error paths — empty form, blocked save, `Cmd+S` still intercepted — become testable once that PR lands. **`pnpm-lock.yaml` is dead weight.** CI runs `npm ci` and the Dockerfile copies `package-lock.json` only, so nothing reads the pnpm lockfile — but it still conflicts on every dependency change (it is one of the three conflicts in #561 right now). I left it alone rather than deleting it unasked; worth a one-line decision. --------- Co-authored-by: ralksta <ralksta@users.noreply.github.com>
…615) Closes #606. ## What The enabled + authenticated check was copy-pasted into **24 handlers across 16 route files**. All of them are now defined inside a single `withAdmin()` wrapper in `lib/admin/withAdmin.ts`. Net effect: **−164 lines**, and the two refusals keep their distinct meanings exactly as the hand-written copies had them — `403` when the admin panel is switched off for the deployment, `401` when it is on and the caller is not signed in. `/api/admin/auth` is deliberately left alone: it is the login endpoint and must stay reachable without a session. ## Why it is worth doing The repetition was not the problem. What it made possible was: a new admin route protected by nobody is a diff that looks fine, and nothing fails when the guard is forgotten. Two structural tests now close that off: - no handler may be exported outside the wrapper (`export async function GET` and friends are rejected) - no route may reach for `isAdminEnabled` / `isAdminAuthenticated` directly The existing table-driven suite is untouched and still proves that every route answers 403 and 401 to an unauthenticated caller — so the behaviour is pinned from both sides: what the routes do, and how they do it. ## Verification - `npx tsc --noEmit` clean - `npx eslint app/api/admin lib/admin` clean - full suite: **920 tests in 68 files, all passing** — including `admin-guards.test.ts`, which is what makes this refactor safe: the 403/401 behaviour of all 24 handlers was already covered before the change. - I checked the new structural test actually fails on a bare `export async function GET` rather than passing vacuously. ## Review notes - The transformation was mechanical (`export async function X(args) {` + guard block → `export const X = withAdmin(async (args) => {`), then prettier. `thumbnail/[id]/route.ts` has a large diff purely from re-indentation. - No behaviour change is intended anywhere. If you want the 403/401 distinction collapsed into one code, say so — I kept it because the existing tests assert both. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
…841) README: 419 → 97 lines. gallery-config.md: 1099 → 360 lines. Nothing deleted; content moved into guides. **README split** - `docs/features.md`: full feature list, screenshots, security table - `docs/immich-setup.md` (new): Immich version, API key permissions, full-size images, album allowlist. Single source for the permission table - `docs/deployment.md`: first-run setup and environment variables - `docs/admin-panel.md`: admin screenshots - `CONTRIBUTORS.md`: credits **gallery-config.md split** (headings unchanged, so anchors keep their names) - `gallery-config.md`: gallery.yaml structure (hero, albums, subpages, per-album options, photo order, content pages, UUIDs) - `site-settings.md`: grid, site behaviour, navigation, watermark, SEO, footer, about - `gallery-features.md`: passwords, proofing, proofing links, photo zoom, originals download - `security.md`: rate limiting, trusted proxies, image token revocation **Archive**: brainstorms, deep dive, ideas, journal design notes, old plans moved to `docs/archive/` (`git mv`). `docs/admin-ux-concept.md` stays because code comments reference it. **Links**: every link to a moved section is rewritten (README, guides, CHANGELOG, `.env.local.example`, the doctor's links in `DiagnosticsView.tsx`). Link check over all Markdown files (paths and anchors), before and after: no new broken link; the three remaining hits are placeholders in an archived note. Verified: prettier, `tsc --noEmit` clean, 2707 unit tests pass. Not in this PR: a test that keeps the permission table in sync with the code. --------- Co-authored-by: ralksta <ralksta@users.noreply.github.com>
…ht backgrounds (#842) - Add top and bottom gradient scrims to Lightbox overlay for reliable legibility - Add frosted backdrop, border, and drop-shadow to close button and counter - Increase --lightbox-control-bg opacity to 0.65 to guarantee WCAG AA 4.5:1 on pure white - Adjust resting control opacity to 0.85 (and 1.0 when zoom is engaged) - Enhance studio-modern preset controls with dark blur backdrop and box-shadow - Add regression test asserting controls clear 4.5:1 against #ffffff
#604) (#855) First part of #604. The admin panel now has a language of its own: `adminLang` in `settings.yaml`, which falls back to the site's `lang`, so an English panel can run over a German site and the other way round. It can be picked under Settings → General → Admin panel language, and the panel switches as soon as the settings are saved. This PR translates the frame and the two screens the issue names. That covers sign-in, the sidebar and its status dropdown, the overview, the save bar, every settings section, and diagnostics including the doctor findings. It also covers the shared pieces those screens use: the draft notice, the "changed elsewhere" dialog, the confirm dialog defaults, the load/retry state and the On/Off words of toggle switches. The page builder, the journal studio, messages, proofing, analytics and help are still English and will follow in separate PRs. How it works: - The admin dictionaries live in `lib/i18n/admin/<locale>/<area>.ts`, separate from the visitor dictionaries, so visitors never download them. They are split by area so the follow-up PRs each add a file instead of editing a shared one. English is the reference type, as on the visitor side. - `app/admin/layout.tsx` resolves the locale on every request and mounts `AdminI18nProvider`. Components call `useAdminDictionary()`. A string that wraps markup uses `{name}` placeholders, filled in by `rich()`, so a translation can put the `<code>` where its grammar needs it. - Doctor checks take the dictionary as a trailing parameter that defaults to English, so `npm run doctor` and the existing tests don't change. The route passes the panel's language. - `lib/__tests__/admin-i18n.test.ts` checks that every locale covers every English key, keeps the same placeholders, and leaves nothing untranslated except the listed shared words. It also checks plural forms, including French putting 0 in the singular. Translations: German, French, Spanish, Italian and Dutch, using the same forms of address as the visitor dictionaries. Still English: messages returned by the API routes (`data.error`, `data.message`), the theme preset names and taglines (shared with the install wizard), collision reasons from `lib/pages`, and Immich's own menu paths quoted in the doctor findings. Part of #604. --------- Co-authored-by: ralksta <ralksta@users.noreply.github.com>
#604) (#856) Second part of #604, following #855. It translates the screens that don't touch the page builder or the journal studio: Messages, Client proofing, Analytics, Help and the backup dialog, plus two shared pieces those screens use: the load errors from `useAdminFetch` and the close button on notifications. - Two new areas, `visitors` (messages, proofing, analytics) and `system` (help, backups), each a file per locale, so nothing in the #855 dictionaries changes beyond the two shared strings. - The Help page now shows the lightbox shortcut names from the visitor dictionary in the panel's language instead of always English, and the notes explaining when a key is absent come from the admin dictionary. A test keeps the English notes identical to the catalogue in `lib/lightboxShortcuts.ts`, so the two cannot drift. - One English text changed on purpose: Help said to open Diagnostics "from the tab in the top bar", which hasn't existed since the sidebar replaced it. It now says the sidebar. - `adminGet()` takes the dictionary as an optional parameter (English by default) and `useAdminFetch` passes the panel's, so the existing tests on its error strings are unchanged. Translated into German, French, Spanish, Italian and Dutch, with the terms the first part already uses. Still English: the page builder, the journal studio and the album/photo pickers they share. Those wait until #852 has settled, since it reworks `AssetPicker`. Checked: all unit tests pass, `tsc` is clean, `npm run build` passes, and the five screens render in German against a real Immich with no console errors. Part of #604. --------- Co-authored-by: ralksta <ralksta@users.noreply.github.com>
OpenStreetMap answers tile requests that carry no `Referer` with an "Access blocked" tile (still HTTP 200, so it doesn't show up as an error in the network tab). Folio sends `Referrer-Policy: strict-origin-when-cross-origin`, which includes the origin, but a proxy in front of the site can replace that header with a stricter one. The map then shows nothing but blocked tiles, which is what #851 reports. Leaflet's `referrerPolicy` option puts the policy on each tile `<img>`, and an element's own policy takes precedence over the page header. Tile requests now send the origin no matter what a proxy does to the header. A browser extension that strips Referers altogether still breaks OSM, but there's nothing Folio can do about that. I reproduced it with curl: the same tile is the normal map with a Referer and the "Access blocked" image without one. Closes #851 Co-authored-by: ralksta <ralksta@users.noreply.github.com>
…hild (#859) Both layouts targeted the tile as if it were the grid cell. The grid's children are the `FadeIn` wrappers, and the tile sits inside one. - **editorial-flow:** `.photo-grid__item:nth-child(3n + 1)` matched every tile, since each is the first child of its wrapper, so every photo spanned the full width and the single–pair–pair rhythm never showed. The rule now counts the grid's own children (`.photo-grid--editorial-flow > :nth-child(3n + 1)`), and the 21:9 shape stays on the tile inside. - **showcase:** `grid-column: 1 / -1` sat on the tile, so the lead photo stayed one column wide. The wrapper of the first photo now gets `photo-grid__cell--featured` and spans the row. The lead tile also no longer takes the photo's own aspect ratio inline, which used to override the stylesheet's 16:9. A portrait lead photo at full width would otherwise be several screens tall. Checked against the real gallery with both layouts under studio-modern (no frame), kunsthalle (passepartout) and cyanotype (shadow), plus editorial-flow at phone width. Measured cell widths, editorial-flow at 1400px: 1304 / 646 / 646 / 1304 / … (kunsthalle: 1160 / 574 / 574 / 1160); showcase: lead 1304, then 427 per column. No console errors. New tests in `app/[...path]/__tests__/grid-layout-cells.test.tsx` check the featured cell class, the lead tile's missing inline ratio, and that `globals.css` no longer counts siblings on the tile. All four fail on dev and pass here. Closes #825 Co-authored-by: ralksta <ralksta@users.noreply.github.com>
…to clips (#860) Downloads and ZIPs removed the location from JPEG and HEIF/AVIF and passed everything else through unchanged. So a video's recorded location went out as stored, and so did the clip that Samsung and Pixel motion photos append after their JPEG, even though the JPEG part was cleaned. **What changes** `lib/mp4Location.ts` walks the MP4/QuickTime box tree while the file streams and only descends into containers that can hold metadata (`moov`, `trak`, `mdia`, `minf`, `udta`, `meta`, `ilst`): - QuickTime `©xyz` and 3GPP `loci` are zeroed and renamed to `free`, which every reader skips. A `loci` that is only zeroed still reads as 0/0. - Apple's QuickTime metadata (`keys` + `ilst`): the values of `…location.ISO6709`, `.name`, `.body` and friends are zeroed. Other keys like make and model stay. If an `ilst` comes before its `keys`, every indexed item is zeroed, since nothing says which one is the location. - The iTunes-style `©xyz` item in `udta/meta/ilst`. - XMP in `udta/XMP_` and in the XMP `uuid` box goes through the same `scrubXmpLocation()` as a photo's. C2PA manifests are zeroed. Nothing moves. The file keeps its length and every `stco`/`co64` offset still points at its sample. `mdat` is never held, so it doesn't matter whether `moov` comes before or after it. A 45 MB QuickTime file went through in ~120 ms with flat memory, and exactly 51 bytes changed. For motion photos, the clip is found by its `ftyp` after the JPEG's EOI and handed to the same walker. From there on the trailer's SOI search stops, so bytes inside the video that look like a JPEG head aren't "scrubbed" as one. That's the concern from the #828 review, and there's a test where that would have damaged the clip. Anything after the clip (Samsung's SEF directory) passes through unchanged. The doctor now counts MP4 and MOV as cleaned, with its texts updated in all six admin languages. The docs say what's still left: GPS **tracks** recorded as timed metadata (GoPro GPMF, Google CAMM, some dashcams). Those samples sit in `mdat` and would need the sample tables, which can arrive after `mdat` has already passed. **Not in this PR:** `/api/video` still streams Immich's playback file to visitors. When Immich didn't transcode a video, that's the original with its location atom, regardless of the download setting. Range requests make that a separate piece of work. **Checked** - Unit tests with synthetic boxes at chunk sizes 1, 3, 7, 64 and 1 MiB (header, `keys` and `ftyp` split across chunks), and a mutation check (disabling the zeroing or the clip hand-off makes them fail). - Real files from ffmpeg: MP4 (`loci`), MOV (`©xyz`), MOV with Apple `mdta` keys, fast-start, and a JPEG + `MotionPhoto_Data` + MP4 motion photo. After scrubbing, `ffprobe` shows no location tag, the size is unchanged and every file decodes without errors. - All unit tests pass, `tsc` is clean, `npm run build` passes. Closes #829 Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Refs #862 ## What changes Video playback through `/api/video` now goes out without the recorded location, matching downloads since #860. A player asks for ranges and seeks, so the one-pass download scrubber can't run as-is. `lib/videoPlayback.ts` does the work once per video instead: - `planPlayback()` reads the top-level box headers with small ranged requests, fetches the boxes that can carry metadata (`moov`, plus a top-level `udta`, `meta` or `uuid`), runs `lib/mp4Location.ts` over a copy and records which bytes it changed. `mdat` is skipped by its header and never read. - `patchStream()` overwrites those bytes in whatever range is being served. - Plans are cached per asset (500 entries, a few patches each) and tied to the file's length, so a file Immich replaces (a finished transcode) gets a new plan. Concurrent requests share one planning run, and failures aren't cached. Nothing changes length, so `Content-Length` and `Content-Range` stay as Immich sent them. A file that isn't MP4/QuickTime (WebM, AVI) passes through, as it does for downloads. An MP4 whose metadata can't be located isn't played (404, `no-store`), and Immich dropping out while planning answers 503. Video URLs get `?r=2`. They're served `immutable` for a year, so without a new URL browsers and CDNs would keep the copies they already have. ## Checked - Equivalence: for ~40 random ranges plus the typical start, seek and tail requests, at chunk sizes 1, 17 and 4096, the patched range is byte-for-byte the download scrubber's output for the same part of the file. Tested with moov at the end and with fast start. - Real file from ffmpeg (2.4 MB MOV with `©xyz` and Apple location keys), served the way a browser asks for it: planning took 5 reads totalling 2.4 KB, and 6 patches changed 51 bytes. The result is identical to the download scrubber's, `ffprobe` shows no location and the file decodes cleanly. - Route tests: whole file, a range covering `moov`, a range of samples (unchanged), WebM passthrough, refusal without `moov`, and 503 when Immich drops out mid-planning. - All unit tests pass, `tsc` is clean, `npm run build` passes. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Bundles the five open Dependabot updates so they land together instead of rebasing each other one by one: | Package | From | To | Dependabot PR | |---|---|---|---| | `next`, `eslint-config-next` | 16.3.6 | 16.4.2 | #843 | | `react`, `react-dom` | 19.3.0 | 19.3.1 | #848 | | `@playwright/test` | 1.63.0 | 1.64.1 | #850 | | `@vitest/coverage-v8` (+ `vitest` floor) | 4.1.11 | 4.1.12 | #849 | | `axe-core` | 4.13.0 | 4.14.0 | #845 | Both lockfiles are regenerated (`npm install`, `pnpm install --lockfile-only`). What Next 16.4 needed: - **#843 failed CI** on `lib/__tests__/image-url-per-tier.test.ts`: `getImgProps` now takes a prepared image config (sorted sizes, `allSizes`, `output`). The test builds it with `prepareImageConfig()`, the same function `next/image` uses. - **`eslint-config-next` 16.4** turns `react-hooks/set-state-in-effect` (14 findings) and `react-hooks/refs` (4) from warnings into errors. CI doesn't run ESLint, but `npm run lint` would go red. Both stay at `warn` for now. Fixing them touches effects in the lightbox and the admin editors (and `AssetPicker`, which #852 reworks), so that's #864. - ESLint now ignores `.claude/**`. Local worktrees are full checkouts and made `npm run lint` in the main checkout take minutes. Also: `vitest`'s floor goes to `^4.1.12`, the peer that `@vitest/coverage-v8` 4.1.12 requires. `pnpm-lock.yaml` had kept 4.0.18. Checked: `tsc` clean, all 2,827 unit tests pass, `npm run lint` has 0 errors, `npm run build` passes on Next 16.4.2. A dev server against the real gallery answers 200 for home, a subpage, an album, journal, about, map and `/admin` with nothing in the server log. I didn't check hydration in a browser; CI's admin smoke test does. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
## Summary This PR fixes the missing album source in journal block creation. The picker now lists every Immich album (including albums not shared in Immich), and choosing an album lets authors select only the photos they want from **+ Photo**, **+ 2-Photo Pair**, or **+ Photo Grid**. These actions open the picker immediately and create a block only after selection; they no longer insert an empty block. A pair requires exactly two photos and a grid at least three. Escape closes only the topmost album picker when it is open over the asset picker. The creation-studio flow and **+ Photos** (one block per selected photo) remain available. CARTO and installation changes are excluded from this PR and are tracked separately in #858 and #857. ## Type of change - [ ] 🐛 Bug fix - [x] ✨ New feature - [x] 🎨 Style / visual - [ ] ♻️ Refactor - [ ] 🔒 Security fix - [x] 📝 Documentation - [ ] 🛠️ Build / CI ## Checklist - [x] Targets the `dev` branch (see [CONTRIBUTING.md](../CONTRIBUTING.md#branches)) - [x] `npm run build` passes locally - [x] `npx tsc --noEmit` passes (0 type errors) - [ ] `npx vitest run` passes (all tests green) - [x] No secrets / API keys in diff - [x] Documentation updated if needed (`docs/`, `README.md`) - [x] Screenshots attached if there are visual changes ## Validation - TypeScript check, production build, and Prettier checks passed. - Focused journal/picker tests passed: 8 tests across three files, covering all-album visibility, block-specific selection counts, and Escape behavior for stacked dialogs. - Full Vitest run: 2,721/2,722 passed. One existing timing-threshold test in `lib/__tests__/locationScrub.test.ts` exceeded 100 ms under full-suite load (123.8 ms), then passed in isolation (39/39). ## Screenshots (if applicable) <img width="640" height="639" alt="Capture d’écran du 2026-10-20 03-29-06" src="https://github.com/user-attachments/assets/f8768c2f-87c6-4edb-809c-1ad5f74f7843" /> <img width="338" height="213" alt="Capture d’écran du 2026-10-20 03-28-52" src="https://github.com/user-attachments/assets/693f202c-3d1b-401e-ad87-e995fdde88b9" /> <img width="506" height="388" alt="Capture d’écran du 2026-10-20 03-28-43" src="https://github.com/user-attachments/assets/82e21c0b-bb15-43f7-b871-54c2ec2d2cf1" /> --------- Co-authored-by: sgsmcfe <344374139+sgsmcfe@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
## Summary When the setup wizard reused the server-configured API key independently of the URL, submitting a different Immich URL with an empty key could send that configured key to the other host. The wizard now reuses the configured key only when the URL is omitted or normalizes to the configured server; changing servers requires an explicitly supplied key. Environment-only API keys are no longer copied into `content/install.json`. A key already persisted there remains available when setup reuses the configured connection, and the wizard explains when that reuse is safe. ## Type of change - [x] 🐛 Bug fix - [ ] ✨ New feature - [ ] 🎨 Style / visual - [ ] ♻️ Refactor - [ ] 🔒 Security fix - [x] 📝 Documentation - [ ] 🛠️ Build / CI ## Checklist - [x] Targets the `dev` branch (see [CONTRIBUTING.md](../CONTRIBUTING.md#branches)) - [x] `npm run build` passes locally - [x] `npx tsc --noEmit` passes (0 type errors) - [ ] `npx vitest run` passes (all tests green) - [x] No secrets / API keys in diff - [x] Documentation updated if needed (`docs/`, `README.md`) - [ ] Screenshots attached if there are visual changes ## Validation - TypeScript check and production build passed. - Install-focused tests passed: 41 tests across `lib/__tests__/install.test.ts` and `app/install/__tests__/InstallWizard.test.tsx`. - The full Vitest suite was not entirely green in the previous run because an unrelated timing-threshold test failed under full-suite load; it passed in isolation. - Prettier check passed for changed files. ## Screenshots (if applicable) N/A — no visual change beyond setup guidance text. --------- Co-authored-by: sgsmcfe <344374139+sgsmcfe@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: ralksta <ralksta@users.noreply.github.com>
…604) (#867) Third and last part of #604, following #855 and #856. It translates the page builder, the journal studio and the pickers they share, so the whole admin panel now follows `adminLang` in all six languages. - **Three new areas:** `pages`, `journal` and `pickers`, with one file per locale under `lib/i18n/admin/<locale>/` and about 560 keys. English stays byte for byte as before, and the existing tests pass unchanged apart from one `.map(createBlock)` call that now passes the block type explicitly. - **`pages`** covers `PageBuilder`, everything in `page-builder/`, `EssayBlockEditor` and the route header (`PageBuilderHeader`, since the route is a server component). The seed names for a new subpage and section, the sort labels and the starter essay follow the panel language. They're passed into `galleryOps`/`sortOptions` the same way `systemHealth` takes its words: a trailing parameter that defaults to English. - **`journal`** covers `JournalStudio`, `JournalEditor`, `BlockFields`, the story and page settings, the preview frame, template names and descriptions, and the starting text of new heading, paragraph and quote blocks. - **`pickers`** covers `AlbumPicker`, `AssetPicker`, `AssetOrderEditor`, `SortableBlocks`, `BlockBadge`, and the drag & drop announcements for screen readers (`dndAnnouncements(label, t)`, now read in the panel language). - **Slug-collision reasons:** `describeCollision()` (`lib/pages.ts`) takes its words as a trailing parameter too. `NewPageDialog`, `PagePanel` and the doctor's page check pass `pages.collision` in the panel language; the API keeps the English default, and a test holds that default equal to the English dictionary. - **Parity test fix:** `invoke()` in `admin-i18n.test.ts` now tries a count, lists and strings as arguments. Before, it crashed on functions whose first argument is a list or a label (`legacyRefs`, the dnd sentences), and that would have hidden untranslated keys. **Still English, on purpose** - Messages the API returns (`data.error`, `data.message`, `data.warning`). - The text a journal template inserts into a new entry. It's the author's content, and the template names around it are translated. - Theme preset names and taglines, which are shared with the install wizard. **Checked** - All unit tests pass, including the parity checks for every locale; the identical-to-English allowances are the words that really are the same (Album, Layout, URL, Text, Live, …). `tsc` is clean, `npm run lint` has 0 errors and `npm run build` passes. - In a browser against the real gallery, with `lang: de`: page builder with an open subpage drawer, journal studio, an entry in the editor, and the photo picker, all German and without console errors. With `adminLang: fr` the same screens are French. Two longer labels used to break badly: "Nouvelle sous-page" wrapped inside its button, and "Albums de cette page" was squeezed to one word per line next to its button. Both now wrap as a whole (`flex-wrap`, `nowrap` labels). I checked French at 1400, 1100 and 390 px, plus Italian and English at 1400. - Translations were made by language-specific agents told to reuse each locale's existing admin terminology. I read the German myself; the other four have only been checked by the parity test. Closes #604 --------- Co-authored-by: ralksta <ralksta@users.noreply.github.com>
…821) (#869) Closes #821. This finishes what #707 started: every admin screen in the issue's list now loads through `useAdminFetch()` or `adminGet()`. That means one wording for failures, which includes the server's reason, plus the session-expired event on a 401 and a retry wherever one makes sense. ## The two silent failures (user-visible) - **Subpage Live Preview:** if `/api/admin/settings` failed, the preview fell back to three columns and English without telling anyone. It now says so above the preview ("uses the default grid and English until the site settings load", followed by the reason) and offers Retry. The preview still renders underneath. - **Journal editor, album picker:** a failed album list opened the picker on an empty list, which reads as "no albums in Immich". The editor now reports the failure and keeps the picker closed, and the next click tries again. This is how JournalStudio already behaved. ## Found along the way - **Album blocks stuck on "loading…":** the effect that fetches an album block's photos cancelled itself on every edit. If the author typed before the answer came back, the answer was dropped, and because the album was already marked as requested it was never asked for again. The cancel is gone; the answer is keyed by album, so it can't be stale. A failed load now shows its reason on the block instead of "loading…" forever. There's a test that fails without the fix. - **Page builder:** a failed album list or page list is still survivable, but it is now reported instead of leaving an empty picker. - **Privacy editor and journal entry:** both used to show a dead-end error and now have a Retry button. Saving over an unread file stays blocked (#595). ## How each screen moved | Screen | Now | Kept from #707 | | --- | --- | --- | | SubpagePreview | `useAdminFetch` | refetched on every open | | ProofingView (list, album choice, a card's picks) | `useAdminFetch`; the hook takes an optional `revision` so a card refetches when the client changes or submits the selection | the `api()` wrapper, for mutations only | | AssetOrderEditor | `useAdminFetch`; the pinned order is derived from the stored list and the live album until the author changes it (no effect copying it into state) | save blocked until loaded | | JournalEditor (entry, album list, album photos) | `adminGet` | drafts, save blocking, stale answers dropped | | AssetPicker | `adminGet` | generation guard, paging, search debounce | | SettingsEditor, AboutSection, PrivacyEditor | `adminGet` | drafts, on-demand About, #595 save blocking | | PageBuilder | `adminGet`, three in parallel | gallery required, albums/pages survivable | | PagePanel | `adminGet`, with a load key instead of resetting state synchronously in the effect | 404 still says "no file for this page" | | AdminDashboard probes | `adminGet` | a failed probe reads as unknown, never broken; `DOCTOR_LEVEL_EVENT` | | DiagnosticsView | `adminGet` | parallel requests; alt text still arrives on its own | Smaller API changes: - `AdminResult` failures now carry `status` (null when no answer arrived), so PagePanel can word the 404 itself. - `diagnostics.errors.failed` takes the reason. - I removed the strings nothing reads anymore: `sessionExpired`/`serverAnswered` and the screen-specific `loadFailed` fallbacks. - The new strings are in all six admin locales. Still on raw `fetch` on purpose: - the AdminShell session check, where a 401 is the expected answer; - the Lightroom export, which is plain text; - JournalStudio's album list, which already reports its failure; - all mutations. ## Checked - `npm run test:unit`: 228 files, 2871 passed. New tests cover every converted screen: `load-errors-shown`, `ProofingView-load`, `AssetPicker-load-errors`, `AssetOrderEditor-load`, `editor-load-errors`, `status-load-errors`, plus a revision case in `useAdminFetch.test`. I ran each new test file against the `dev` version of its components, and every test fails there except the PagePanel 404/switch case, which is a regression guard. - `tsc` is clean, `npm run build` passes, and ESLint has 0 errors with warnings down from 246 to 239 (no file gained one). - In the browser against a copy of the real config (German panel): - The page builder, a subpage's Live Preview, Diagnostics, Proofing, Settings, About, Legal/privacy and a content page all load without errors. - A forced 500 on `/api/admin/settings` shows the preview notice with the reason, and Retry clears it. --------- Co-authored-by: ralksta <ralksta@users.noreply.github.com>
The public stylesheet gets the treatment `admin.css` got in #533/#549. `app/globals.css` (2,338 lines) keeps its theme, tokens and Leaflet imports, and below them becomes the load order for fourteen files in `app/styles/`, each named for what it styles: `a11y`, `header`, `main`, `hero`, `subpage`, `album-grid`, `album`, `states`, `controls`, `footer`, `badges-empty`, `theme-conditionals`, `aesthetics`, `responsive` Cuts are only at existing section banners, contiguous ranges only, nothing reordered. `app/layout.tsx` doesn't change. A comment in `globals.css` says the order is load-bearing (the presets layer on top by specificity, and the responsive rules have to come last). **The two checks from the issue** 1. **Byte for byte:** the fourteen parts, concatenated in import order, are exactly lines 17–2338 of the old file. The split script asserted this before writing anything. 2. **Rule by rule on the built CSS:** I built `dev` and this branch and compared the emitted CSS. - 13 of the 14 CSS files are byte-identical. - The global bundle has the same 911 rules in the same order, plus 4 that the minifier used to drop: `.header__nav-link:hover,.header__nav-link.active`, `.album-card:hover`, `.album-header` and `.footer__link:hover`. Each of these is followed later, in the same context, by a rule with the same selector that sets all of its properties again, so they're dead and the computed styles are unchanged. It's the same effect #549 found once in `admin.css`: the minifier can't merge duplicates across an import boundary. - No theme preset depends on a rule's position inside `globals.css`. The presets are all imported before it, so they only win by specificity, and that relationship is the same as before. **Tests** Four tests read `globals.css` as text. They now use `readGlobalsCss()` (`lib/__tests__/fixtures/globalsCss.ts`), which inlines the parts, and the accent-text check also walks `app/styles/`. All unit tests pass, `tsc` is clean and `npm run build` passes. Closes #556 Co-authored-by: ralksta <ralksta@users.noreply.github.com>
…again (#866) `eslint-config-next` 16.4 reports two React Compiler rules as errors, `react-hooks/set-state-in-effect` and `react-hooks/refs`. #865 kept both at `warn`. This resolves the 18 findings and removes the overrides, so new ones fail `npm run lint` again. **Changed, so the rule has nothing to report** - **Lightbox.** The client-only mount guard and the fullscreen check now come from `useSyncExternalStore` instead of an effect that sets state. The load state and the "Copied" of the previous photo are reset while rendering ("adjusting state when a prop changes"). A side effect: the next photo is never drawn for a frame with the previous one's state. - **LeafletMap.** The latest-value refs (labels, markers, callbacks) are updated in an effect declared before the map's own, instead of being assigned during render. - **AdminDashboard** closes its mobile menu on navigation while rendering. - **BackupManagerModal** clears its last confirmation and messages on reopening while rendering. The list reload stays in its effect. **Kept, with the reason next to it** - **ProofingContext** reads the favourites from `localStorage` and `?proof=` after mount, on purpose: both only exist on the client, and reading them during render would cause a hydration mismatch. - **Loads started on mount**: diagnostics, privacy policy, about page, the sidebar status, the asset picker and the content-page panel. Each keeps its effect with a pointer to #821, which moves them to `useAdminFetch` (that hook already sets state only in callbacks). Converting them here would mean rewriting six load paths, including the behaviours #707 asked to keep. **Checked** - New tests: the Lightbox resets load state and "Copied" on the next photo, and offers the fullscreen key only where `document.fullscreenEnabled` is true. The admin menu closes on a route change, and the backup dialog reopens without the old confirmation. The Lightbox tests fail with the reset removed. - In a browser against the real gallery: the next photo starts as not loaded, `c` shows "Kopiert" and the next photo shows "Link" again, the fullscreen key is listed, `/map` renders tiles and markers, and there are no console errors. - All unit tests pass, `tsc` is clean, `npm run lint` has 0 errors with both rules at their default severity. - A probe merge with the open admin-translation branch (`AssetPicker`, `PagePanel`) merges cleanly and lints clean. Closes #864 Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Closes #877. Every release is already pushed to `ghcr.io/ralksta/immich-folio` (public, `latest` / `x.y` / `x.y.z` / `sha-…`), but nothing in the docs said so: the README and `docs/deployment.md` only described building from a checkout. ## Docs - **README → Docker** leads with a checkout-free Compose file using the image, and points at `/install` plus the log line with the setup token. - **`docs/deployment.md`**: Compose and standalone recipes use the image; new tag table, update command (`docker compose pull`), and building from source as its own subsection. `.env.local` is now optional in the Compose recipe (`required: false`, as in the repo's own `docker-compose.yml`), since the wizard covers a fresh install. - **Doctor commands** said `docker compose exec folio …`. That service doesn't exist (it's `lightbox`), and `exec` runs as root, so the `content/` writability check passed whatever the ownership was. They now read `docker compose exec -u nextjs lightbox npm run doctor`. - `docs/admin-panel.md`: the Docker example's `image: immich-folio` → the ghcr image. ## arm64 The image was amd64-only: the workflow set up QEMU but never passed `platforms`. It now builds both architectures natively, using the [multi-runner pattern from the Docker docs](https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners): - `build` matrix: `linux/amd64` on `ubuntu-24.04`, `linux/arm64` on `ubuntu-24.04-arm`, each pushed by digest with its own GHA cache scope. - `merge` creates the manifest list under the release tags, then runs the Trivy scan and SARIF upload as before. - I dropped `type=ref,event=branch`: a manual run from a branch published a tag named after the branch, which is how the stale `main` tag (v0.9 era) is still on the registry. A manual run now adds only its `sha-` tag. The lockfile already has the arm64-musl builds of `@next/swc` and `sharp`, so the Dockerfile is unchanged. ## Verification - Pulled the current `latest` anonymously and ran it with an empty `content/`: health answers `setup`, `/` shows the setup screen, and the token is in the log. No checkout needed. - `npm run doctor` as `-u nextjs` in the image works (as root it reports a writable `content/` regardless). - actionlint and Prettier are clean. - **Workflow run on this branch** ([run 41201750104](https://github.com/ralksta/immich-folio/actions/runs/41201750104)): both build jobs and the merge passed (arm64 in ~3 min, amd64 ~4 min). `sha-e801777…` on ghcr lists `linux/amd64` and `linux/arm64`. Pulled on amd64, it starts into the setup screen. `latest` is untouched. --------- Co-authored-by: ralksta <ralksta@users.noreply.github.com>
## Summary Adds link-only standalone albums, and the admin controls needed to share them safely. **Unlisted albums** - A top-level `albums:` entry can set `hidden: true`, keeping its own `/<slug>` route and photos while staying out of the header menu, homepage links, sitemap, `/map` and search indexing (`noindex`). JSON-LD is omitted on the page. - Photos still load for anyone with the link, and the direct route returns `200`. - Albums inside a subpage are unchanged: hide the subpage instead. - This is unlisting, not access control. A password can be combined with it. **Custom link** - The album drawer now shows the album's address with a copy button. - The slug can be set explicitly or generated with **Random link** for an unguessable URL. - Custom slugs are validated against built-in routes and subpages, checked for collisions, and used consistently by routing, nav, sitemap, map and the doctor. - Changing a slug changes the address; old links stop working, with no redirect. **Admin recovery** - The builder now catches a subpage with no albums before sending, naming the page instead of surfacing a server 400. - The floating save bar has **Discard changes**, so a stale or broken draft can always be reset to the saved version. No new routes or dependencies. `hidden` and `slug` are preserved by the page builder and only read on standalone album entries. ## Type of change - [x] New feature - [x] Bug fix (admin draft/save recovery) ## Checklist - [x] Targets the `dev` branch - [x] `npm run build` passes locally - [x] `npx tsc --noEmit` passes (0 type errors) - [x] `npx vitest run` passes in CI - On a Windows checkout, only the pre-existing file-permission/POSIX-path assertions fail (`chmod 0600`, POSIX paths, `rename` semantics). - [x] No secrets / API keys in diff - [x] Documentation updated (`docs/gallery-config.md`, `docs/admin-panel.md`, `docs/features.md`, `content/gallery.yaml.example`) ## Testing - `npx tsc --noEmit` — clean. - `npm run build` — passes. - 220 targeted tests pass, covering config derivation, routing/slug lookup, sitemap/feed visibility, map listings, metadata, admin round-tripping and i18n parity. - End-to-end against the mock Immich server: - toggled **Unlisted** in the admin and saved; the PUT returned `200` and wrote `hidden: true`; - the album disappeared from the homepage nav and the admin card showed the unlisted badge; - the direct URL still returned `200` with `noindex` and no JSON-LD; - toggling it back republished the home link; - set a custom slug, saved, and confirmed the new URL served `200` while the old one returned `404`; - generated a random link and copied it from the drawer; - removing a subpage's last album was stopped before the request with a message naming the page, and **Discard changes** restored the saved state.
#873) Closes #691. The same concept carried several names across the panel, the visitor strings and the docs, so an owner had to learn that *Page*, *Subpage* and *Collection* are one thing while "Photo Essay" meant two different features. ## What changed | Concept | Before | Now | | --- | --- | --- | | A named group of albums | Subpage / Collection | **Page** (`subpages` in YAML) | | A `content/pages/*.md` entry | Page | **Content Page** | | A dated post under `/journal` | Story, entry, article | **Journal entry** | | `grid.layout: essay` | "Photo Essay Mode", "Storytelling & Photo Essay Builder" | **Story Layout** | | An album's `heroImage` | "Custom Hero Image" | **Cover Photo** (Hero stays the homepage hero) | - **One layout table.** Settings › Grid, an album's override and a page's layout each carried their own copy of the labels; they had drifted (the page sheet lacked `justified`, described `uniform` differently). They now read `common.layouts` through `lib/admin/layouts.ts`. - **One name for one action.** Diagnostics' "Clear cache" and the header's "Reload" hit the same endpoint; both say **Reload** now, and the tooltip/cache detail explain that it also re-reads the config. - **English visitor copy.** Sentence case throughout, no emoji or arrows inside strings — the heart, envelope and arrow are markup now, so a screen reader no longer announces "red heart" and a translation can place them. `lib/__tests__/i18n.test.ts` gains a check for both rules. - **Docs.** A vocabulary table in `docs/admin-panel.md` with the unchanged YAML keys beside each name; the journal guide stops calling entries "photo essays"; the gallery docs use the US spelling "favorites" like the panel and Immich. ## Backward compatibility Nothing a running deployment stores changes: - **YAML keys unchanged** — `subpages`, `albums`, `sections`, `heroImage`, `grid.layout: essay`, `proofing` keep their names; only labels move. - **URLs, slugs and cookie names unchanged.** - **Content files unchanged** — no migration, no rewrite on the next admin save. - **Visitor copy for a page keeps saying "Collection"** in the kicker and the `/map` meta line. That is deliberately site copy, not the panel's vocabulary, and changing it would touch every visitor locale; the issue's table is scoped to owner-facing names. Say the word if you want "Page" there too — it is two strings per locale. - The API's default collision text (`COLLISION_TEXT_EN`) says "is already a page", matching the panel. ## Already fixed before this PR Five bullets on the issue were stale: the password gate already has a per-type subtitle (`siteSubtitle`/`pageSubtitle`/`journalSubtitle`), the legal page already cites the DDG, the footer says "Sign out" (not "Logout"), the theme metadata already lives once in `PRESET_REGISTRY`, and proofing already speaks of a "Selection". The `albumEntries.ts` comment claiming `download` has no UI is corrected. ## Verification - `npx tsc --noEmit` — clean apart from the repo's existing stale-`.next` and `image-url-per-tier` errors (the latter from this machine's outdated `node_modules`). - `npm run test:unit` — 2893 passed with the pinned dependencies (230 files). - `npm run build` — passes (exit 0) with the pinned dependencies. - `npx eslint` / `prettier --check` on the touched files — clean. - Not live-tested against a real Immich instance; every changed string is display-only apart from the two layout `<select>`s, which now read the shared table. --------- Co-authored-by: ralksta <ralksta@users.noreply.github.com>
… (#881) Moves the map, Leaflet popup/marker and admin panel rules out of `app/themes/studio-modern.css` into the stylesheets that own those surfaces: - `/map` header and container overrides (`[data-preset='studio-modern'] .map-page__*`, `.map-container`) → `app/map/map.css` - Shared Leaflet marker and popup overrides (`[data-preset='studio-modern'] .map-marker`, `.leaflet-popup-*`, `.map-popup__*`) → `app/leaflet.css` - Admin token overrides (`[data-preset='studio-modern'] .admin-layout` dark and light) → `app/admin/styles/tokens.css`, folding in the contrast-corrected `--admin-text-muted` and `--admin-on-accent` values from #795 ### Tests - `lib/__tests__/preset-registry.test.ts` now asserts that no preset stylesheet in `app/themes/*.css` styles `.admin-layout`, `.map-page`, `.map-container`, `.map-marker`, `.map-popup` or `.leaflet-*`. - `lib/__tests__/accent-text.test.ts` includes `app/map/map.css` in the raw-accent text check. Closes #613 Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Follow-up to #873: the panel renamed **Story Settings** to **Entry Settings**, but `docs/journal.md` still used the old name in the text and in the screenshot's alt text. The screenshot's dialog title is the old one too; it'll be updated with the next screenshot refresh. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Bumps the nextjs group with 2 updates: [next](https://github.com/vercel/next.js) and [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next). Updates `next` from 16.4.2 to 16.4.3 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/vercel/next.js/releases">next's releases</a>.</em></p> <blockquote> <h2>v16.4.3</h2> <blockquote> <p>[!NOTE] This release is backporting bug fixes. It does <strong>not</strong> include all pending features/changes on canary.</p> </blockquote> <h3>Core Changes</h3> <ul> <li>[lts-active] Fix extensionless Google font URLs in both bundlers (<a href="https://redirect.github.com/vercel/next.js/issues/100277">#100277</a>)</li> <li>[lts-active] Don't load the app-page runtime in proxy.ts (<a href="https://redirect.github.com/vercel/next.js/issues/100423">#100423</a>)</li> <li>[lts-active] Revert "Turbopack: loader change invalidation (<a href="https://redirect.github.com/vercel/next.js/issues/95732">#95732</a>)" (<a href="https://redirect.github.com/vercel/next.js/issues/100438">#100438</a>)</li> <li>[lts-active][cd] Gate release bot credentials behind GitHub environments (<a href="https://redirect.github.com/vercel/next.js/issues/100123">#100123</a>)</li> <li>[lts-active] test: Pin react-virtualized (<a href="https://redirect.github.com/vercel/next.js/issues/100545">#100545</a>)</li> <li>[lts-active][cd] Move release un-draft to a follow-up job in bot-autonomous (<a href="https://redirect.github.com/vercel/next.js/issues/100572">#100572</a>)</li> <li>[lts-active] Keep OpenTelemetry request traces together (<a href="https://redirect.github.com/vercel/next.js/issues/100349">#100349</a>, <a href="https://redirect.github.com/vercel/next.js/issues/100294">#100294</a>, <a href="https://redirect.github.com/vercel/next.js/issues/100324">#100324</a>, <a href="https://redirect.github.com/vercel/next.js/issues/100350">#100350</a>, <a href="https://redirect.github.com/vercel/next.js/issues/100552">#100552</a>, <a href="https://redirect.github.com/vercel/next.js/issues/100583">#100583</a>) (<a href="https://redirect.github.com/vercel/next.js/issues/100542">#100542</a>)</li> <li>[lts-active] Avoid loading standalone React in server bootstrap (<a href="https://redirect.github.com/vercel/next.js/issues/100610">#100610</a>)</li> </ul> <h3>Credits</h3> <p>Huge thanks to <a href="https://github.com/mischnic"><code>@mischnic</code></a>, <a href="https://github.com/lukesandberg"><code>@lukesandberg</code></a>, <a href="https://github.com/acdlite"><code>@acdlite</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>, and <a href="https://github.com/sokra"><code>@sokra</code></a> for helping!</p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/vercel/next.js/commit/29bbeab09981148ade186e8599ecdede15abc281"><code>29bbeab</code></a> v16.4.3</li> <li><a href="https://github.com/vercel/next.js/commit/5117201d795c7a4d185bad044cef545be0b4e504"><code>5117201</code></a> [lts-active] Avoid loading standalone React in server bootstrap (<a href="https://redirect.github.com/vercel/next.js/issues/100610">#100610</a>)</li> <li><a href="https://github.com/vercel/next.js/commit/37d603bf4d91cd4ecf97b9090b1dde61d84a2296"><code>37d603b</code></a> [lts-active] Keep OpenTelemetry request traces together (<a href="https://redirect.github.com/vercel/next.js/issues/100349">#100349</a>, <a href="https://redirect.github.com/vercel/next.js/issues/100294">#100294</a>, <a href="https://redirect.github.com/vercel/next.js/issues/1">#1</a>...</li> <li><a href="https://github.com/vercel/next.js/commit/cf9d27d96c7d9efa5b7ba0a5a30d2817b01364e7"><code>cf9d27d</code></a> [lts-active][cd] Move release un-draft to a follow-up job in bot-autonomous (...</li> <li><a href="https://github.com/vercel/next.js/commit/3f6a632d413e57004718297543677caea7219ba4"><code>3f6a632</code></a> [lts-active] test: Pin react-virtualized (<a href="https://redirect.github.com/vercel/next.js/issues/100545">#100545</a>)</li> <li><a href="https://github.com/vercel/next.js/commit/f715bba5e2d7ce4f76cb41d2ed06ee44ac71dc9a"><code>f715bba</code></a> [lts-active][cd] Gate release bot credentials behind GitHub environments (<a href="https://redirect.github.com/vercel/next.js/issues/10">#10</a>...</li> <li><a href="https://github.com/vercel/next.js/commit/c864c78fb14b79f47c152e17d7fce64f3b88f1db"><code>c864c78</code></a> [lts-active] Revert "Turbopack: loader change invalidation (<a href="https://redirect.github.com/vercel/next.js/issues/95732">#95732</a>)" (<a href="https://redirect.github.com/vercel/next.js/issues/100438">#100438</a>)</li> <li><a href="https://github.com/vercel/next.js/commit/f3ff943085e07643be8ab18435611d6c259e8cb2"><code>f3ff943</code></a> [lts-active] Don't load the app-page runtime in proxy.ts (<a href="https://redirect.github.com/vercel/next.js/issues/100423">#100423</a>)</li> <li><a href="https://github.com/vercel/next.js/commit/f697cf5db456a846ea2f83ef16a85d34afbda3d5"><code>f697cf5</code></a> [lts-active] Fix extensionless Google font URLs in both bundlers (<a href="https://redirect.github.com/vercel/next.js/issues/100277">#100277</a>)</li> <li>See full diff in <a href="https://github.com/vercel/next.js/compare/v16.4.2...v16.4.3">compare view</a></li> </ul> </details> <br /> Updates `eslint-config-next` from 16.4.2 to 16.4.3 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/vercel/next.js/releases">eslint-config-next's releases</a>.</em></p> <blockquote> <h2>v16.4.3</h2> <blockquote> <p>[!NOTE] This release is backporting bug fixes. It does <strong>not</strong> include all pending features/changes on canary.</p> </blockquote> <h3>Core Changes</h3> <ul> <li>[lts-active] Fix extensionless Google font URLs in both bundlers (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100277">#100277</a>)</li> <li>[lts-active] Don't load the app-page runtime in proxy.ts (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100423">#100423</a>)</li> <li>[lts-active] Revert "Turbopack: loader change invalidation (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/95732">#95732</a>)" (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100438">#100438</a>)</li> <li>[lts-active][cd] Gate release bot credentials behind GitHub environments (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100123">#100123</a>)</li> <li>[lts-active] test: Pin react-virtualized (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100545">#100545</a>)</li> <li>[lts-active][cd] Move release un-draft to a follow-up job in bot-autonomous (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100572">#100572</a>)</li> <li>[lts-active] Keep OpenTelemetry request traces together (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100349">#100349</a>, <a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100294">#100294</a>, <a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100324">#100324</a>, <a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100350">#100350</a>, <a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100552">#100552</a>, <a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100583">#100583</a>) (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100542">#100542</a>)</li> <li>[lts-active] Avoid loading standalone React in server bootstrap (<a href="https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next/issues/100610">#100610</a>)</li> </ul> <h3>Credits</h3> <p>Huge thanks to <a href="https://github.com/mischnic"><code>@mischnic</code></a>, <a href="https://github.com/lukesandberg"><code>@lukesandberg</code></a>, <a href="https://github.com/acdlite"><code>@acdlite</code></a>, <a href="https://github.com/eps1lon"><code>@eps1lon</code></a>, and <a href="https://github.com/sokra"><code>@sokra</code></a> for helping!</p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/vercel/next.js/commit/29bbeab09981148ade186e8599ecdede15abc281"><code>29bbeab</code></a> v16.4.3</li> <li>See full diff in <a href="https://github.com/vercel/next.js/commits/v16.4.3/packages/eslint-config-next">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#893) Supersedes #874, #875 and #876, which conflicted with the lockfile after #891 and did not rebase on request. Same versions, one lockfile update. tsc 0, lint 0 errors, 2905 unit tests pass; Prettier 3.9.10 flags no file that 3.9.9 did not. Co-authored-by: ralksta <ralksta@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release v0.22.0. See the [0.22.0] section of CHANGELOG.md: unlisted albums with custom links (#878), the ghcr.io image for amd64 and arm64 (#879), one vocabulary across the admin panel (#873), Next.js 16.4.3 (#891), zod/Prettier (#893), studio-modern.css cleanup (#881).