diff --git a/src/content/docs/changelog/index.mdx b/src/content/docs/changelog/index.mdx index cf4928c7..f01d8a12 100644 --- a/src/content/docs/changelog/index.mdx +++ b/src/content/docs/changelog/index.mdx @@ -33,6 +33,7 @@ Notable changes to the kit, newest first. - **Frontend: no more idle GPU load from the background (fix).** Both apps animated the `body` background glows forever (`fsh-aurora`, a `background-position` drift that can't be composited), repainting the full viewport and its blur/blend layers every frame even when the page sat idle. The glows are now static at the same position. See [#1394](https://github.com/fullstackhero/dotnet-starter-kit/pull/1394) and [#1368](https://github.com/fullstackhero/dotnet-starter-kit/issues/1368). - **Identity: two people editing the same profile no longer silently overwrite each other (fix).** `PUT /api/v1/identity/profile` replaces the whole representation and carried no concurrency token, so overlapping saves resolved as last-write-wins with the losing change gone and no error raised anywhere - and because both front-ends compensate with a client-side read-modify-write, a save built on a stale read confidently echoed every old field back over the newer write. `GET /profile` now returns the user's `ConcurrencyStamp` as a strong `ETag`, and `PUT /profile` honours `If-Match`: a stale token is answered with `412 Precondition Failed` and nothing is written. The header is optional, so existing callers are unaffected; `If-Match: *` is accepted, a weak validator never matches (`If-Match` mandates strong comparison), and a malformed header is a `400` rather than a `412` that would trap a client in an unwinnable retry loop. The precondition runs before any storage call, so a rejected update never orphans an uploaded avatar nor clears the current one. Two smaller fixes came with it: Identity's own `ConcurrencyFailure` result, previously surfacing as a generic `500`, now maps to the same `412`, and the sign-in refresh no longer runs when the update failed. No migration - `AspNetUsers.ConcurrencyStamp` was already an EF Core concurrency token. The tenant dashboard sends the tag from the read that seeded the form and deliberately does **not** auto-retry on `412`: resending the body built from the values the user saw, against a freshly fetched tag, performs the very overwrite the `412` rejected. It keeps the edits on screen, adopts the current version, and asks for a deliberate re-save. See [Identity](/docs/modules/identity/). - **CORS: the framework now exposes `ETag` and allows `If-Match`.** An `ETag`/`If-Match` contract is invisible to a browser on another origin unless the server says so: `ETag` is not a CORS-safelisted response header, and the kit's policy never called `WithExposedHeaders`, so a front-end read `null` and silently stopped sending the precondition. The policy now exposes `ETag` on both the permissive and restricted branches, and `if-match` ships in `CorsOptions.AllowedHeaders` in `appsettings.json` and `appsettings.Production.json`. If you replace the policy with your own, keep both, otherwise the profile precondition above degrades back to a lost update with no error to notice. +- **Docker Compose: Postgres starts again on a fresh machine (fix).** The compose file runs `postgres:18-alpine` but mounted the data volume at `/var/lib/postgresql/data`, a layout the 18+ images refuse even on an empty volume, so the container restart-looped and `migrator` and `api` never started. The volume now mounts at `/var/lib/postgresql`, where 18+ keeps its versioned data directory. **Upgrade note:** a `pg_data` volume created by Postgres 17 or earlier has to be dumped and restored to run on 18 regardless of the mount; with the new mount an old volume is not read and the database starts empty (nothing is deleted). Take the backup in `deploy/docker/README.md` first. See [#1397](https://github.com/fullstackhero/dotnet-starter-kit/pull/1397). ## 2026-08-07