Skip to content

fix: rolling-rebase audit follow-ups — library-sync favorite leak, re-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) - #794

Merged
Deeds67 merged 6 commits into
mainfrom
worktree-issue-743-audit-followups
Jul 17, 2026
Merged

fix: rolling-rebase audit follow-ups — library-sync favorite leak, re-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743)#794
Deeds67 merged 6 commits into
mainfrom
worktree-issue-743-audit-followups

Conversation

@Deeds67

@Deeds67 Deeds67 commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Closes #743.

Follow-ups surfaced during the 2026-07-02 rolling-rebase audit remediation — each verified during a fix slice but left out of scope at the time. This clears the actionable items (1–4, 6); item 5 (benign migration-timestamp collisions) is grandfathered and intentionally needs no code change.

Changes

1. LibraryAssetSync leaked the owner's isFavorite to all library members (c8dc666)
Connected-libraries sync streamed the raw asset.isFavorite to every member. Adds a syncLibraryAsset column set (syncAsset minus the favorite) and applies the same per-row ownership mask SharedSpaceAssetSync / AlbumAssetSync use — owner keeps the flag, everyone else sees false — on both the backfill and upsert paths.

2. handleVideoTrim race on rapid re-trim (70f8942)
Overlapping trims shared the frame path {id}_edited.mp4.frame.jpg (one job's cleanup unlinked the frame another was reading → "Input file is missing") and ffmpeg overwrote {id}_edited.mp4 in place while the previous job could still be probing it. Now serializes trims per-asset (in-process AsyncLock), uses a unique per-invocation temp frame path (covers cross-process overlap), and trims to a temp file that's atomically renamed into place.

3. Re-author the Dart nullable_items_in_arrays patch for v3 templates (f52de2d, d1a177c)
The patch that types nullable-item arrays as List<T?> was orphaned — generate-dart-sdk.sh stopped applying it after the upstream three-state DTO rework (immich-app#27231) rewrote the same template regions, so TimeBucketAssetResponseDto declared its 8 nullable-item array fields as non-nullable and a null city/thumbhash/duration item threw at decode time instead of surfacing in the type system. Re-authored against the current template (required, x-is-optional, and plain-optional branches), re-inserted into the SDK build, dropped the now-redundant stack hand-patch, and added a preflight guard so re-orphaning is caught. Verified: full mobile suite (2520 tests) + dart analyze --fatal-infos green on the regenerated SDK.

4. Four more un-branded i18n keys containing "Immich" (fb36761)
admin.asset_offline_description was the #672 bug shape again (a top-level override clobbered the user-facing string while the admin key kept leaking). Adds/fixes overrides for it plus import_option_skip_duplicates, my_immich_title, my_immich_description (the my.immich.app redirect keeps its domain). verify-branding.sh / test-i18n-branding.sh only checked keys that had overrides, so a leaking un-overridden key passed silently — both now scan every string value in the branded en.json.

6. micromatch parens quirk in the ownership-manifest matcher (2a315e4)
micromatch parses bare (user) as an empty regex capture group, so route-group globs like web/src/routes/(user)/spaces/** silently matched nothing in coverage.ts (both the ignore filter and the glob classifier). Ports the literal-parens escaping the S20 manifest guard already uses.

Not addressed (by design)

Item 5 — the two remaining benign migration-timestamp collisions are grandfathered by the S17 guard; renaming is unsafe on a continuously-deployed fork (Kysely missing-migration hard-fail). Tracked only for visibility.

Testing

  • Server: media.service.spec.ts + medium sync-library-asset.spec.ts added
  • Tools: coverage.spec.ts + dart-nullable-array-items.spec.ts guards added
  • Branding: test-i18n-branding.sh hardened to scan all string values
  • Mobile: full test suite (2520) + dart analyze --fatal-infos green on regenerated SDK

Deeds67 added 6 commits July 16, 2026 22:16
…h globs (#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.
Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place
…#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.
…sing-override gap (#743)

- asset_offline_description was the #672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.
…ch (#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.
@Deeds67 Deeds67 added the changelog:fix Bug fix for changelog label Jul 17, 2026
@Deeds67 Deeds67 changed the title fix(#743): rolling-rebase audit follow-ups (library-sync favorite leak, re-trim race, Dart nullable-items patch, branding/i18n, ownership-glob) fix: rolling-rebase audit follow-ups — library-sync favorite leak, re-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) Jul 17, 2026
@Deeds67
Deeds67 merged commit 8873747 into main Jul 17, 2026
46 of 48 checks passed
Deeds67 added a commit that referenced this pull request Jul 18, 2026
…-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) (#794)

* fix(tools): escape route-group parens in ownership-coverage micromatch globs (#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.

* fix(server): make rapid re-trims of the same video race-free (#743)

Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place

* fix(server): mask isFavorite in LibraryAssetSync for non-owned assets (#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.

* fix(branding): brand the 4 remaining Immich i18n keys + close the missing-override gap (#743)

- asset_offline_description was the #672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.

* fix(mobile): re-author the Dart nullable-items-in-arrays template patch (#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.

* chore(server): satisfy require-await in the trim-serialization test (#743)
Deeds67 added a commit that referenced this pull request Jul 20, 2026
…-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) (#794)

* fix(tools): escape route-group parens in ownership-coverage micromatch globs (#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.

* fix(server): make rapid re-trims of the same video race-free (#743)

Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place

* fix(server): mask isFavorite in LibraryAssetSync for non-owned assets (#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.

* fix(branding): brand the 4 remaining Immich i18n keys + close the missing-override gap (#743)

- asset_offline_description was the #672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.

* fix(mobile): re-author the Dart nullable-items-in-arrays template patch (#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.

* chore(server): satisfy require-await in the trim-serialization test (#743)
Deeds67 added a commit that referenced this pull request Jul 21, 2026
…-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) (#794)

* fix(tools): escape route-group parens in ownership-coverage micromatch globs (#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.

* fix(server): make rapid re-trims of the same video race-free (#743)

Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place

* fix(server): mask isFavorite in LibraryAssetSync for non-owned assets (#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.

* fix(branding): brand the 4 remaining Immich i18n keys + close the missing-override gap (#743)

- asset_offline_description was the #672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.

* fix(mobile): re-author the Dart nullable-items-in-arrays template patch (#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.

* chore(server): satisfy require-await in the trim-serialization test (#743)
Deeds67 added a commit that referenced this pull request Jul 22, 2026
…-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) (#794)

* fix(tools): escape route-group parens in ownership-coverage micromatch globs (#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.

* fix(server): make rapid re-trims of the same video race-free (#743)

Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place

* fix(server): mask isFavorite in LibraryAssetSync for non-owned assets (#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.

* fix(branding): brand the 4 remaining Immich i18n keys + close the missing-override gap (#743)

- asset_offline_description was the #672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.

* fix(mobile): re-author the Dart nullable-items-in-arrays template patch (#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.

* chore(server): satisfy require-await in the trim-serialization test (#743)
Deeds67 added a commit that referenced this pull request Jul 22, 2026
…-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) (#794)

* fix(tools): escape route-group parens in ownership-coverage micromatch globs (#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.

* fix(server): make rapid re-trims of the same video race-free (#743)

Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place

* fix(server): mask isFavorite in LibraryAssetSync for non-owned assets (#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.

* fix(branding): brand the 4 remaining Immich i18n keys + close the missing-override gap (#743)

- asset_offline_description was the #672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.

* fix(mobile): re-author the Dart nullable-items-in-arrays template patch (#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.

* chore(server): satisfy require-await in the trim-serialization test (#743)
Deeds67 added a commit that referenced this pull request Jul 23, 2026
…-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) (#794)

* fix(tools): escape route-group parens in ownership-coverage micromatch globs (#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.

* fix(server): make rapid re-trims of the same video race-free (#743)

Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place

* fix(server): mask isFavorite in LibraryAssetSync for non-owned assets (#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.

* fix(branding): brand the 4 remaining Immich i18n keys + close the missing-override gap (#743)

- asset_offline_description was the #672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.

* fix(mobile): re-author the Dart nullable-items-in-arrays template patch (#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.

* chore(server): satisfy require-await in the trim-serialization test (#743)
Deeds67 added a commit that referenced this pull request Jul 23, 2026
…-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) (#794)

* fix(tools): escape route-group parens in ownership-coverage micromatch globs (#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.

* fix(server): make rapid re-trims of the same video race-free (#743)

Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place

* fix(server): mask isFavorite in LibraryAssetSync for non-owned assets (#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.

* fix(branding): brand the 4 remaining Immich i18n keys + close the missing-override gap (#743)

- asset_offline_description was the #672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.

* fix(mobile): re-author the Dart nullable-items-in-arrays template patch (#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.

* chore(server): satisfy require-await in the trim-serialization test (#743)
Deeds67 added a commit that referenced this pull request Jul 24, 2026
…-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) (#794)

* fix(tools): escape route-group parens in ownership-coverage micromatch globs (#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.

* fix(server): make rapid re-trims of the same video race-free (#743)

Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place

* fix(server): mask isFavorite in LibraryAssetSync for non-owned assets (#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.

* fix(branding): brand the 4 remaining Immich i18n keys + close the missing-override gap (#743)

- asset_offline_description was the #672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.

* fix(mobile): re-author the Dart nullable-items-in-arrays template patch (#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.

* chore(server): satisfy require-await in the trim-serialization test (#743)
Deeds67 added a commit that referenced this pull request Jul 25, 2026
…-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) (#794)

* fix(tools): escape route-group parens in ownership-coverage micromatch globs (#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.

* fix(server): make rapid re-trims of the same video race-free (#743)

Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place

* fix(server): mask isFavorite in LibraryAssetSync for non-owned assets (#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.

* fix(branding): brand the 4 remaining Immich i18n keys + close the missing-override gap (#743)

- asset_offline_description was the #672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.

* fix(mobile): re-author the Dart nullable-items-in-arrays template patch (#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.

* chore(server): satisfy require-await in the trim-serialization test (#743)
ya4ept pushed a commit to ya4ept/Noodle_gallery that referenced this pull request Jul 30, 2026
…-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (open-noodle#743) (open-noodle#794)

* fix(tools): escape route-group parens in ownership-coverage micromatch globs (open-noodle#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.

* fix(server): make rapid re-trims of the same video race-free (open-noodle#743)

Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place

* fix(server): mask isFavorite in LibraryAssetSync for non-owned assets (open-noodle#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.

* fix(branding): brand the 4 remaining Immich i18n keys + close the missing-override gap (open-noodle#743)

- asset_offline_description was the open-noodle#672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.

* fix(mobile): re-author the Dart nullable-items-in-arrays template patch (open-noodle#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.

* chore(server): satisfy require-await in the trim-serialization test (open-noodle#743)
Deeds67 added a commit that referenced this pull request Aug 10, 2026
…-trim race, Dart nullable-items patch, branding/i18n, ownership-glob (#743) (#794)

* fix(tools): escape route-group parens in ownership-coverage micromatch globs (#743)

micromatch parses bare `(user)` as an empty regex capture group, so
ownership globs like `web/src/routes/(user)/spaces/**` silently matched
nothing in coverage.ts (both the ignore filter and the glob classifier).
Port the literal-parens escaping the S20 manifest guard already uses.

* fix(server): make rapid re-trims of the same video race-free (#743)

Overlapping trim jobs shared the frame path `{id}_edited.mp4.frame.jpg`
(one job's cleanup unlinked the frame the other was reading -> 'Input
file is missing') and ffmpeg overwrote `{id}_edited.mp4` in place while
the previous job could still be probing it.

- serialize trims per asset id (AsyncLock, in-process)
- unique per-invocation temp frame path (covers cross-process overlap)
- trim to a temp file and atomically rename into place

* fix(server): mask isFavorite in LibraryAssetSync for non-owned assets (#743)

LibraryAssetSync streamed the raw asset.isFavorite to every library
member. Apply the same per-row ownership mask SharedSpaceAssetSync /
AlbumAssetSync use (owner keeps the flag, everyone else sees false) on
both the backfill and upsert paths.

* fix(branding): brand the 4 remaining Immich i18n keys + close the missing-override gap (#743)

- asset_offline_description was the #672 bug shape again: overridden at
  the TOP LEVEL with the admin-block text, clobbering the user-facing
  string while admin.asset_offline_description kept leaking 'Immich'.
  Both nesting levels now carry their own branded text.
- add overrides for import_option_skip_duplicates, my_immich_title,
  my_immich_description (the my.immich.app redirect service keeps its
  domain name).
- verify-branding.sh and test-i18n-branding.sh only checked keys that
  HAVE overrides, so a leaking key with no override passed silently —
  both now scan every string value in the branded en.json.

* fix(mobile): re-author the Dart nullable-items-in-arrays template patch (#743)

The patch that types nullable-item arrays as List<T?> was orphaned:
generate-dart-sdk.sh stopped applying it after the upstream three-state
DTO template rework (immich-app#27231) rewrote the same template regions, so
TimeBucketAssetResponseDto declared its 8 nullable-item array fields
as non-nullable and a null city/thumbhash/duration item threw at decode
time instead of surfacing in the type system.

- re-author the hunks against the current (post-immich-app#27231) template,
  covering required, x-is-optional, and plain-optional declarations;
  the rebuild only kicks in when items.isNullable so no other model
  regenerates differently
- re-insert the patch application into generate-dart-sdk.sh (after
  native_class.mustache.patch, which its hunks are authored against)
- drop the stack hand-patch (time_bucket_asset_response_dto.dart.patch)
  — the template now produces the same declaration
- add a preflight guard: the script must keep applying the patch, and
  every nullable-item array in the spec must be List<T?> in the
  committed generated model (this is the guard that would have caught
  the orphaning)

The old patch's third hunk (implicit-optional number parsing) is
obsolete: the x-is-optional pipeline now handles absent/null numbers.

Verified: full mobile test suite (2520 tests) + dart analyze
--fatal-infos green on the regenerated SDK.

* chore(server): satisfy require-await in the trim-serialization test (#743)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog:fix Bug fix for changelog 🗄️server

Projects

None yet

1 participant