Skip to content

Angular upgrade to v20#576

Merged
uldisrudzitis merged 16 commits intomasterfrom
enh-angular-upgrade-to-v20
Oct 8, 2025
Merged

Angular upgrade to v20#576
uldisrudzitis merged 16 commits intomasterfrom
enh-angular-upgrade-to-v20

Conversation

@uldisrudzitis
Copy link
Copy Markdown
Collaborator

@uldisrudzitis uldisrudzitis commented Oct 6, 2025

Summary by CodeRabbit

  • New Features

    • App initializes Sentry at startup; added sentry:sourcemaps script and Sentry config/service/initializer.
    • Added setting formats: icon-readonly, route, action.
  • Refactor

    • UI templates converted to a new control-flow templating syntax (no intended behavioral changes).
  • Chores

    • Angular upgraded to v20, package and Node engine bumps, tooltip library swapped, TypeScript moduleResolution changed, .gitignore updated.
    • Protractor E2E tests and docs removed; backend Sentry env/config key and new config option added.

@uldisrudzitis uldisrudzitis self-assigned this Oct 6, 2025
@uldisrudzitis uldisrudzitis added Enhancement dependencies Pull requests that update a dependency file labels Oct 6, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 6, 2025

Walkthrough

Adds runtime Sentry initialization via APP_INITIALIZER and a new SentryConfigService, upgrades Angular/tooling and Node engines, swaps tooltip library, changes TypeScript moduleResolution to "bundler", extends a settings format union, removes Protractor E2E, and mass-refactors component templates from Angular structural directives to @if/@for/@switch-style syntax.

Changes

Cohort / File(s) Summary
Tooling & Config
editor/angular.json, editor/package.json, editor/tsconfig.json, editor/.gitignore
Enables production source maps, adds top-level schematics, bumps package versions and Node engine constraints, adds @sentry/angular/@sentry/cli, swaps tooltip package, sets TS moduleResolution to bundler, and ignores .sentryclirc.
App bootstrap & Sentry
editor/src/app/app.module.ts, editor/src/app/sentry/sentry-config.service.ts, editor/src/app/sentry/sentry-init.factory.ts, editor/src/app/sentry.error-handler.ts
Removes old SentryErrorHandler file/provider, adds SentryConfigService and sentryInitFactory, wires APP_INITIALIZER, registers Sentry error handler via Sentry.createErrorHandler(...), and provides Sentry.TraceService.
Dependency/module swap
editor/src/app/sites/sites-shared.module.ts, editor/package.json
Replaces @amin-karimi/ng2-tooltip-directive with ngx-tooltip-directives and updates dependency entries.
Type additions
editor/src/app/shared/interfaces.ts
Extends SettingConfigModel.format union with 'icon-readonly', 'route', and 'action'.
Global template refactor
editor/src/app/... (many components, see list)
Large-scale template rewrite: replaces *ngIf/*ngFor/ngSwitch with @if/@for/@switch-style blocks across numerous components; templates reflowed while component APIs remain unchanged.
Inputs & small UI components
editor/src/app/inputs/*, editor/src/app/header/*, editor/src/app/header/preview-toggle.component.ts
Converts conditional rendering and iteration syntax in input and header templates to the new templating form; preserves bindings and handlers.
Shop UI changes
editor/src/app/shop/*
Converts loops/conditionals to @for/@if; shop-regional-costs adds inline per-region cost add/edit template markup (template-only changes).
Sites — media, galleries & templates
editor/src/app/sites/media/*, editor/src/app/sites/template-settings/*, editor/src/app/sites/site-media.component.ts, editor/src/app/sites/media/entry-gallery-image-editor.component.ts, editor/src/app/sites/media/entry-gallery.component.ts
Replaces structural directives with @if/@for; removes some unused NgIf imports; entry-gallery.component.ts stops injecting Store and removes derived site/mediaUrl properties.
Sections & Settings rendering
editor/src/app/sites/sections/*, editor/src/app/sites/settings/*, editor/src/app/sites/shared/*
Reworks settings rendering to @for/@if/@switch constructs and restructures settings panels/rows; component APIs preserved.
Lists & misc templates
editor/src/app/sites/*, editor/src/app/themes/themes.component.ts, editor/src/app/user/user-account.component.ts
Replaces list and conditional templates with new syntax and minor formatting/signature tweaks.
Backend Sentry endpoint & config
_api_app/.env.example, _api_app/app/Http/Controllers/StateController.php, _api_app/config/sentry.php, _api_app/composer.json
Renames env var to SENTRY_FRONTEND_DSN, controller reads that var, adds enable_logs config option, and bumps Sentry Laravel package to 4.16.0.
E2E removal
editor/README.md, editor/e2e/*
Removes Protractor E2E instructions and deletes Protractor config, tests, page objects, and E2E tsconfig files.
Lint / tooling cleanup
editor/src/tslint.json, editor/tslint.json
Deletes local and root TSLint configuration files.
Minor formatting / small edits
editor/src/app/pipes/pipe.ts, editor/src/app/sites/site-media.component.ts (type formatting), various small files
Minor formatting/comment removals and small signature reflows (e.g., selectedSections type formatting).

Sequence Diagram(s)

sequenceDiagram
  participant App as Angular App Bootstrap
  participant Init as APP_INITIALIZER (sentryInitFactory)
  participant Svc as SentryConfigService
  participant HTTP as HttpClient
  participant Sentry as @sentry/angular

  App->>Init: invoke APP_INITIALIZER
  Init->>Svc: getSentryDsn()
  Svc->>HTTP: GET /_api/v1/sentry-dsn (text)
  alt DSN returned
    HTTP-->>Svc: DSN string
    Svc-->>Init: DSN
    Init->>Sentry: Sentry.init({ dsn })
    Note right of Sentry: Sentry initialized
  else no DSN / error
    HTTP-->>Svc: error or empty
    Svc-->>Init: ''
    Note right of Init: skip Sentry.init()
  end
  Init-->>App: initialization completes
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

I hop through templates, nibbling tags anew,
Schematics sprout leaves and TypeScript finds a view.
Sentry wakes at bootstrap, dsn fetched with care,
Tooltips swapped and formats sprinkled everywhere.
A rabbit cheers this tidy refactor — hop, hooray! 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely summarizes the primary change in the pull request, namely upgrading the project’s Angular version to v20, which aligns with the extensive version bumps and template refactoring in the diff. It is specific enough for someone reviewing history to understand the main purpose of the changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch enh-angular-upgrade-to-v20

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f34c8e5 and 96a0e1e.

📒 Files selected for processing (1)
  • editor/src/.browserslistrc (1 hunks)
🔇 Additional comments (1)
editor/src/.browserslistrc (1)

2-2: LGTM! Cosmetic improvements enhance readability.

The blank lines added between comment sections improve the file's readability. The browserslist queries are syntactically correct, and the previous issue with invalid > prefixes on lines 8-10 has been resolved.

Also applies to: 4-4, 6-6


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
editor/src/app/inputs/files-input.component.ts (1)

76-83: Remove MP4 exception from file-size check
Current condition only rejects oversized non-MP4 files, allowing MP4s of any size. Enforce the generic limit instead:

-    if (file.type !== 'video/mp4' && file.size > this.maxFileSize) {
+    if (file.size > this.maxFileSize) {
editor/src/app/inputs/file-input.component.ts (2)

109-117: Guard against empty selection; avoid files[0] NPE.

Change event can fire with no file. Accessing files[0].size will throw.

-  validate(fileInput: HTMLInputElement) {
-    if (fileInput.files[0].size > this.maxFileSize) {
+  validate(fileInput: HTMLInputElement) {
+    const file = fileInput.files && fileInput.files.length ? fileInput.files[0] : null;
+    if (!file) {
+      return false;
+    }
+    if (file.size > this.maxFileSize) {
       this.error = `File exceeds the maximum file-size limit ${
         this.maxFileSize / 1024 / 1024
       } MB`;
       return false;
     }
     return true;
   }

89-89: disabled should be boolean, not string.

String-typed disabled can coerce truthy unexpectedly (e.g., "false"). Bindings to [disabled] require boolean.

-  @Input() disabled: string;
+  @Input() disabled: boolean = false;

Ensure callers pass boolean values.

editor/src/app/sites/media/entry-gallery.component.ts (1)

23-59: Guard the loop source; track by a stable key.

If mediaCacheData or file is undefined, @for will error. Also track by src for stability.

-        <div class="entry-gallery-items">
-          @for (file of entry.mediaCacheData.file; track file) {
+        <div class="entry-gallery-items">
+          @if (entry?.mediaCacheData?.file?.length) {
+          @for (file of entry.mediaCacheData.file; track file['@attributes'].src) {
             <div
               class="entry-gallery-item"
               >
             @if (file['@attributes'].type === 'image') {
               <div class="media image">
-                  <img
-              src="{{ currentSite.mediaUrl }}/{{ entry.mediafolder }}/_smallthumb_{{ file['@attributes'].src }}"
-                    />
+                  <img
+                    [src]="currentSite.mediaUrl + '/' + entry.mediafolder + '/_smallthumb_' + file['@attributes'].src"
+                    [attr.alt]="file['@attributes'].src"
+                  />
               </div>
             }
             @if (file['@attributes'].type === 'video') {
               <div class="media video">
                 <video
                   [attr.poster]="
                     file['@attributes'].poster_frame
                       ? currentSite.mediaUrl +
                         '/' +
                         entry.mediafolder +
                         '/' +
                         file['@attributes'].poster_frame
                       : null
                   "
                     >
-                    <source
-                src="{{ currentSite.mediaUrl }}/{{ entry.mediafolder }}/{{ file['@attributes'].src }}"
-                      type="video/mp4"
-                      />
+                    <source
+                      [src]="currentSite.mediaUrl + '/' + entry.mediafolder + '/' + file['@attributes'].src"
+                      type="video/mp4"
+                    />
                 </video>
               </div>
             }
             </div>
-          }
+          }
+          }
editor/src/app/sites/media/entry-gallery-image-editor.component.ts (1)

219-248: Guard against undefined imageCroppedEvent in updateSize()

this.imageCroppedEvent can be undefined before the first crop event, causing a runtime error when editing width/height immediately after ready.

   updateSize(e) {
-    let value = parseInt(e.value);
+    if (!this.imageCroppedEvent) {
+      return;
+    }
+    let value = parseInt(e.value);
@@
-    if (e.field === 'width') {
+    if (e.field === 'width') {
+      const ev = this.imageCroppedEvent;
       const x2 =
-        this.imageCroppedEvent.cropperPosition.x1 +
-        value *
-          (this.imageCroppedEvent.cropperPosition.x2 /
-            this.imageCroppedEvent.imagePosition.x2);
+        ev.cropperPosition.x1 +
+        value * (ev.cropperPosition.x2 / ev.imagePosition.x2);
       this.position = {
-        ...this.imageCroppedEvent.cropperPosition,
+        ...ev.cropperPosition,
         x2,
       };
     }
@@
-    if (e.field === 'height') {
+    if (e.field === 'height') {
+      const ev = this.imageCroppedEvent;
       this.position = {
-        ...this.imageCroppedEvent.cropperPosition,
+        ...ev.cropperPosition,
         y2:
-          this.imageCroppedEvent.cropperPosition.y1 +
-          value *
-            (this.imageCroppedEvent.cropperPosition.y2 /
-              this.imageCroppedEvent.imagePosition.y2),
+          ev.cropperPosition.y1 +
+          value * (ev.cropperPosition.y2 / ev.imagePosition.y2),
       };
     }
editor/src/app/sites/site.component.ts (1)

32-48: Boolean guard + ARIA for action buttons

  • Using a tri-state modificationDisabled is brittle. Make it a boolean and set explicitly.
  • Add aria-labels to improve accessibility.
-          @if (!modificationDisabled) {
+          @if (!modificationDisabled) {
             <button
               [attr.title]="
                 site['@attributes'].published > 0 ? 'Unpublish' : 'Publish'
               "
+              [attr.aria-label]="
+                site['@attributes'].published > 0 ? 'Unpublish site' : 'Publish site'
+              "
               (click)="
                 updateField(
                   '@attributes.published',
                   site['@attributes'].published > 0 ? '0' : '1'
                 )
               "
             >

Outside this range (component class), consider:

// Prefer explicit booleans
modificationDisabled: boolean = false;

ngOnInit() {
  this.hostname = location.hostname;
  this.modificationDisabled = this.site.name === '';
}

Also applies to: 52-60

editor/src/app/sites/settings/site-settings.component.ts (1)

327-339: Guard against null user/appState in helpers

user$ and appState$ may be undefined initially; add defensive checks to prevent runtime errors.

   isDisabled(
     settingGroup: SettingsGroupModel,
     setting: SettingModel,
     config: SettingConfigModel,
     user: UserStateModel
   ) {
-    return (
-      this.settingUpdate[settingGroup.slug + ':' + setting.slug] ||
-      (config.requires_feature &&
-        user.features.indexOf(config.requires_feature) === -1)
-    );
+    const updating = !!this.settingUpdate[settingGroup.slug + ':' + setting.slug];
+    const lacksFeature =
+      !!config.requires_feature &&
+      (!user || !user.features || user.features.indexOf(config.requires_feature) === -1);
+    return updating || lacksFeature;
   }
   disabledReason(
     config: SettingConfigModel,
     user: UserStateModel,
     appState: AppStateModel
   ) {
-    if (
-      !config.requires_feature ||
-      user.features.indexOf(config.requires_feature) > -1
-    ) {
-      return;
-    }
+    if (!config?.requires_feature || !user?.features) return;
+    if (user.features.indexOf(config.requires_feature) > -1) return;
-    const requiredPlan = appState.plans.find(
+    const requiredPlan = appState?.plans?.find(
       (plan) => plan.features.indexOf(config.requires_feature) > -1
     );
     if (!requiredPlan) {
       return;
     }

Also applies to: 340-360

🧹 Nitpick comments (44)
editor/src/app/inputs/files-input.component.ts (3)

72-73: Allow re‑selecting the same files

Reset the input value after emit so a user can pick the same file(s) again.

-    this.updateField(fileInput.files);
+    this.updateField(fileInput.files as FileList);
+    fileInput.value = '';

45-49: Use stable tracking to avoid duplicate collision

Tracking by the error string collapses identical messages. Track by index.

-      @for (error of errors; track error) {
+      @for (error of errors; track $index) {

7-7: Null‑safe error class

Avoids potential undefined access before ngOnInit.

-      [class.error]="errors.length > 0"
+      [class.error]="(errors?.length ?? 0) > 0"
editor/tsconfig.json (1)

9-14: Align module with bundler resolution for Angular 20 toolchain

With "moduleResolution": "bundler", it's recommended to output modern ES modules. Consider updating "module" to ES2022 (or ESNext) to match target and avoid legacy transforms.

Apply:

-    "module": "es2020",
+    "module": "ES2022",

Optionally, use "ESNext" if your bundler (esbuild/Vite) prefers latest syntax. Verify local build and tests.

editor/src/app/user/user-account.component.ts (1)

13-19: Add ARIA for status messages

Improve accessibility so screen readers announce feedback.

Apply:

-          <div class="error-message">
+          <div class="error-message" role="alert" aria-live="assertive">
             {{ error }}
           </div>
-            <div class="info-message">{{ message }}</div>
+            <div class="info-message" aria-live="polite">{{ message }}</div>

Also applies to: 53-55

editor/src/app/shop/orders/shop-orders.component.ts (1)

11-12: Use stable track keys and provide an empty state

  • Track by a stable id to reduce DOM churn.
  • Provide @empty to handle no orders.

Apply:

-    @for (order of orders$ | async; track order) {
+    @for (order of orders$ | async; track order.order_id) {
-                    @for (item of order.orders; track item) {
+                    @for (item of order.orders; track $index) {
-          }
+          } @empty {
+            <p class="setting">No orders found.</p>
           }

If some orders lack order_id, choose another unique key (e.g., order.uid). Optionally hoist currency with @let to avoid multiple async subscriptions:

Example:

@let currency = (currency$ | async);
...
<span>{{ currency }} {{ stringToCurrency(order.shipping) }}</span>

Also applies to: 117-118, 128-129

editor/src/app/shop/products/shop-products.component.ts (1)

19-26: Track by stable keys to minimize re-renders

Use identifiers instead of object identity.

Apply:

-    @for (group of productGroups$ | async; track group) {
+    @for (group of productGroups$ | async; track group.name) {
...
-        @for (product of group.products; track product) {
+        @for (product of group.products; track product.id) {

Verify all groups have unique name and products have id. If not, fallback to track $index.

editor/src/app/sites/sites.component.ts (1)

19-25: Track sites by a unique field

Avoid tracking by object identity.

Apply:

-      @for (site of sitesList; track site) {
+      @for (site of sitesList; track site.name) {

Confirm site.name is unique; otherwise, use another stable identifier.

editor/src/app/inputs/inline-text-input.component.ts (2)

93-110: Use KeyboardEvent.key; drop deprecated keyCode checks.

keyCode is deprecated. Rely on .key only for Escape/Enter.

-    if (
-      $event instanceof KeyboardEvent &&
-      ($event.key === 'Escape' || $event.keyCode === 27)
-    ) {
+    if ($event instanceof KeyboardEvent && $event.key === 'Escape') {
       ($event.target as HTMLInputElement).value = this.lastValue;
       ($event.target as HTMLInputElement).blur();
       return;
     }
@@
-    if (
-      $event instanceof KeyboardEvent &&
-      !($event.key === 'Enter' || $event.keyCode === 13)
-    ) {
+    if ($event instanceof KeyboardEvent && $event.key !== 'Enter') {
       return;
     }

64-67: Keep lastValue in sync with external value changes.

If parent updates [value] while not focused, Escape will revert to stale lastValue. Update lastValue in ngOnChanges when value changes and focus is false.

Additions outside the shown lines:

// imports
import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';

// class signature
export class InlineTextInputComponent implements OnInit, OnChanges {
  // ...

  ngOnChanges(changes: SimpleChanges) {
    if ('value' in changes && !this.focus) {
      this.lastValue = this.value;
    }
  }
}
editor/src/app/inputs/select-input.component.ts (1)

82-83: Type blurTimeoutId precisely.

Use ReturnType to avoid any.

-  blurTimeoutId: any;
+  blurTimeoutId: ReturnType<typeof setTimeout> | null = null;
editor/src/app/inputs/file-input.component.ts (3)

15-15: Show the selected file name instead of “[object File]”.

When value is a File, interpolation prints “[object File]”. Provide a display accessor.

-          <div class="file-input">{{ value || 'Add file...' }}</div>
+          <div class="file-input">{{ displayValue || 'Add file...' }}</div>

Add in the class (outside shown lines):

get displayValue(): string | null {
  if (!this.value) return null;
  return this.value instanceof File ? this.value.name : String(this.value);
}

102-107: Allow re-selecting the same file; clear input after handling change.

Browsers don’t fire change if the same file is reselected unless you reset the input value.

  onChange(fileInput: HTMLInputElement) {
    if (!this.validate(fileInput) || this.disabled) {
      return;
    }
-    this.updateField(fileInput.files[0]);
+    this.updateField(fileInput.files![0]);
+    // Reset to allow selecting the same file again
+    fileInput.value = '';
  }

119-129: Also clear the native input when removing a file.

Keeps UI/state consistent and enables re-uploading the same file.

  removeFile($event, fileInput: HTMLInputElement) {
     $event.stopPropagation();
     $event.preventDefault();

     if (this.disabled) {
       return;
     }

     this.value = '';
+    if (fileInput) {
+      fileInput.value = '';
+    }
     this.updateField(this.value);
  }
editor/src/app/popup/popup.component.ts (2)

80-128: Unsubscribe on destroy to avoid leaks.

Component subscribes to a service stream but never unsubscribes.

-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';

-export class PopupComponent implements OnInit {
+export class PopupComponent implements OnInit, OnDestroy {
+  private subscription: { unsubscribe(): void } | null = null;

   ngOnInit() {
-    this.service.subscribe({
+    this.subscription = this.service.subscribe({
       next: (popupState: PopupState) => {
         // ...
       },
       error: (err) => {
         console.error(err);
       },
     });
   }
+
+  ngOnDestroy() {
+    this.subscription?.unsubscribe();
+  }

If the service returns an RxJS Subscription, this pattern will prevent retained component instances.


25-35: Consider a stable track key for actions.

Tracking by object identity can cause unnecessary re-renders. Use index or a semantic key if available (e.g., action.id or action.label).

-              @for (action of actions; track action) {
+              @for (action of actions; track $index) {
editor/src/app/sites/media/entry-gallery.component.ts (1)

73-75: Remove or use the unused mediaUrl field.

mediaUrl is computed but not referenced in the template or class.

editor/src/app/sites/media/entry-gallery-image-editor.component.ts (1)

89-98: Make the Crop button actually disabled when action isn’t allowed

Currently only a CSS class toggles. Users can still click and trigger crop.

-              <button
+              <button
                 type="button"
                 class="button"
                 [class.disabled]="!canCrop"
+                [disabled]="!canCrop"
                 (click)="cropImage()"
                 >
                 Crop
               </button>
editor/src/app/sites/media/entry-gallery-editor.component.ts (1)

356-358: Avoid function in @for track; use a stable property expression

Using identifyGalleryItem($index, file) causes extra calls and binds. Track directly by a unique property.

-          @for (
-            file of currentEntry.mediaCacheData.file; track identifyGalleryItem($index,
-            file)) {
+          @for (
+            file of currentEntry.mediaCacheData.file;
+            track file['@attributes'].src
+          ) {
editor/src/app/themes/themes.component.ts (1)

109-114: Harden window.open against tabnabbing

Add noopener (and nullify opener for Safari).

-          window.open(url, '_blank');
+          const win = window.open(url, '_blank', 'noopener');
+          if (win) {
+            try {
+              win.opener = null;
+            } catch {}
+          }
editor/src/app/sites/media/site-media.component.ts (2)

35-66: Use stable keys in @for track to reduce churn.

Tracking by whole objects is fragile. Prefer stable primitives.

  • Sections: track section.section.name
  • Tags: track tag['@attributes'].name

Example:

-      @for (section of sectionsList; track section) {
+      @for (section of sectionsList; track section.section.name) {
...
-          @for (tag of section.tags; track tag) {
+          @for (tag of section.tags; track tag['@attributes'].name) {

72-87: Avoid calling component methods in track; use direct expressions.

Method calls in track run every CD pass. Use property expressions instead.

-      @for ( selectedSection of selectedSections; track identifySection($index, selectedSection.section)) {
+      @for (selectedSection of selectedSections; track selectedSection.section.name) {
...
-        } @for (entry of selectedSection.entries; track identifyEntry($index, entry)) {
+        } @for (entry of selectedSection.entries; track entry) {

You can then remove identifySection/identifyEntry or keep for other uses.

editor/src/app/sites/sections/section.component.ts (2)

91-101: Track params by a stable key, not the object.

Reduces re-renders when array identity changes.

-          @for (param of params; track param) {
+          @for (param of params; track (param.setting?.id ?? param.setting?.name)) {

34-48: Use consistent string values for 'published' to match the model.

Model declares published?: '0' | '1'. Consider removing numeric 1 for consistency.

-            [1, '1'].includes(section['@attributes'].published)
+            section['@attributes'].published === '1'

And similarly in the [published] binding.

editor/src/app/shop/regional-costs/shop-regional-costs.component.ts (2)

22-37: Track regions by id rather than object.

Prevents unnecessary DOM churn when arrays are recreated.

-    @for (region of regionalCosts$ | async; track region) {
+    @for (region of regionalCosts$ | async; track region.id) {

52-82: Track costs by id.

Same rationale as regions.

-        @for (cost of region.costs; track cost) {
+        @for (cost of region.costs; track cost.id) {
editor/package.json (1)

18-26: Align Angular package versions and TypeScript range

  • Bump all "@angular/*" deps (except cdk) to "^20.3.4" to match build-angular/cli and avoid peer/version mismatches.
  • Ensure devDependency "typescript" is >=5.9.0 <6.0.0 as required by Angular v20.3.
editor/src/app/shop/settings/shop-settings.component.ts (1)

17-35: Use stable identifiers in @for track clauses

Track by slug to avoid unnecessary re-renders when arrays are recreated.

-    @for (settingGroup of settings$ | async; track settingGroup) {
+    @for (settingGroup of settings$ | async; track settingGroup.slug) {
       <div class="subgroup">
         <div class="setting">
           <h4>{{ settingGroup.config.title || settingGroup.slug }}</h4>
         </div>
-        @for (setting of settingGroup.settings; track setting) {
+        @for (setting of settingGroup.settings; track setting.setting.slug) {
           <berta-setting
             [setting]="setting.setting"
             [config]="setting.config"
             [disabled]="
               settingUpdate[settingGroup.slug + ':' + setting.setting.slug]
             "
             [error]="settingError[settingGroup.slug + ':' + setting.setting.slug]"
             (update)="updateSetting(settingGroup.slug, $event)"
           ></berta-setting>
         }
       </div>
     }
editor/src/app/sites/sections/site-sections.component.ts (1)

39-50: Track @for items by a stable key (section name)

Tracking the object reference may cause churn when arrays are recreated. Use the section name.

-      @for (sd of sectionsList; track sd) {
+      @for (sd of sectionsList; track sd.section.name) {
         <berta-section
           cdkDrag
           [section]="sd.section"
           [isExpanded]="sd.section.name === currentSection"
           [params]="sd.params"
           [templateSectionTypes]="sectionTypes$ | async"
           [translations]="translations$ | async"
           (inputFocus)="updateComponentFocus($event)"
           (update)="updateSection(sd, $event)"
         ></berta-section>
       }
editor/src/app/shared/interfaces.ts (1)

45-50: Keep Response and Model format unions in sync

You added new literals to SettingConfigModel.format but SettingConfigResponse.format doesn’t include them. Align to reduce mapping casts and future type errors.

Proposed update in SettingConfigResponse:

   format?:
     | 'text'
     | 'longtext'
+    | 'url'
+    | 'richtext'
     | 'select'
     | 'fontselect'
     | 'toggle'
     | 'icon'
+    | 'icon-readonly'
     | 'image'
     | 'color'
+    | 'route'
+    | 'action'
     | boolean;
editor/src/app/sites/shared/setting-row.component.ts (1)

11-20: Use direct @for iteration and stable track key (avoid keyvalue churn).

Iterate the array directly and track by a stable slug to reduce re-renders; also simplify the class check.

Apply:

-        @for (inputField of inputFields | keyvalue; track inputField) {
+        @for (inputField of inputFields; track inputField.setting.slug) {
           <berta-setting
-          [class.bt-auto-width]="
-            ['icon-readonly'].indexOf(inputField.value.config.format) > -1
-          "
-            [setting]="inputField.value.setting"
-            [config]="inputField.value.config"
+          [class.bt-auto-width]="inputField.config.format === 'icon-readonly'"
+            [setting]="inputField.setting"
+            [config]="inputField.config"
             (update)="updateField($event)"
           ></berta-setting>
         }
editor/src/app/header/preview-toggle.component.ts (1)

14-41: Collapse to a single @if/@else to avoid double evaluation.

Current template duplicates conditions. Use @if/@else for clarity.

-      @if (!isPreviewActive) {
-        <svg
-          xmlns="http://www.w3.org/2000/svg"
-          width="20"
-          height="12.6"
-          version="1.1"
-          viewBox="0 0 20 12.6"
-          >
-          <path
-            d="m18.5 6.3q-1.7-2.6-4.2-4 0.6 1.1 0.6 2.5 0 2.1-1.5 3.5-1.5 1.5-3.5 1.5-2.1 0-3.5-1.5-1.5-1.5-1.5-3.5 0-1.4 0.6-2.5-2.5 1.2-4.2 4 1.5 2.2 3.7 3.6 2.2 1.4 4.8 1.4 2.6 0 4.8-1.4 2.2-1.4 3.7-3.6zm-7.9-4.2q0-0.2-0.1-0.4t-0.4-0.1q-1.4 0-2.3 1-1 1-1 2.3 0 0.2 0.1 0.4 0.1 0.1 0.4 0.1t0.4-0.1q0.1-0.1 0.1-0.4 0-1 0.6-1.6 0.6-0.6 1.6-0.6 0.2 0 0.4-0.1 0.1-0.1 0.1-0.4zm9.4 4.2q0 0.4-0.2 0.7-1.5 2.6-4.2 4.1-2.6 1.5-5.6 1.5-2.8 0-5.6-1.5-2.6-1.5-4.2-4.1-0.2-0.4-0.2-0.7t0.2-0.7q1.6-2.5 4.2-4.1 2.6-1.5 5.6-1.5 2.8 0 5.6 1.5 2.6 1.5 4.2 4.1 0.2 0.4 0.2 0.7z"
-            stroke-width="0"
-            />
-        </svg>
-      }
-      @if (isPreviewActive) {
-        <svg
-          xmlns="http://www.w3.org/2000/svg"
-          width="20"
-          height="18.3"
-          version="1.1"
-          viewBox="0 0 20 18.3"
-          >
-          <path
-            d="m4.6 15v1.7h-4.6v-1.7zm4.6-1.7q0.3 0 0.6 0.2 0.2 0.2 0.2 0.6v3.3q0 0.3-0.2 0.6-0.2 0.2-0.6 0.2h-3.3q-0.3 0-0.6-0.2-0.2-0.2-0.2-0.6v-3.3q0-0.3 0.2-0.6 0.2-0.2 0.6-0.2zm2.1-5v1.7h-11.2v-1.7zm-8.3-6.7v1.7h-2.9v-1.7zm17.1 13.3v1.7h-9.6v-1.7zm-12.5-15q0.3 0 0.6 0.2 0.2 0.2 0.2 0.6v3.3q0 0.3-0.2 0.6-0.2 0.2-0.6 0.2h-3.3q-0.3 0-0.6-0.2-0.2-0.2-0.2-0.6v-3.3q0-0.3 0.2-0.6 0.2-0.2 0.6-0.2zm8.3 6.7q0.3 0 0.6 0.2t0.2 0.6v3.3q0 0.3-0.2 0.6t-0.6 0.2h-3.3q-0.3 0-0.6-0.2-0.2-0.2-0.2-0.6v-3.3q0-0.3 0.2-0.6 0.2-0.2 0.6-0.2zm4.2 1.7v1.7h-2.9v-1.7zm0-6.7v1.7h-11.2v-1.7z"
-            stroke-width="0"
-            />
-        </svg>
-      }
+      @if (isPreviewActive) {
+        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="18.3" version="1.1" viewBox="0 0 20 18.3">
+          <path d="m4.6 15v1.7h-4.6v-1.7zm4.6-1.7q0.3 0 0.6 0.2 0.2 0.2 0.2 0.6v3.3q0 0.3-0.2 0.6-0.2 0.2-0.6 0.2h-3.3q-0.3 0-0.6-0.2-0.2-0.2-0.2-0.6v-3.3q0-0.3 0.2-0.6 0.2-0.2 0.6-0.2zm2.1-5v1.7h-11.2v-1.7zm-8.3-6.7v1.7h-2.9v-1.7zm17.1 13.3v1.7h-9.6v-1.7zm-12.5-15q0.3 0 0.6 0.2 0.2 0.2 0.2 0.6v3.3q0 0.3-0.2 0.6-0.2 0.2-0.6 0.2h-3.3q-0.3 0-0.6-0.2-0.2-0.2-0.2-0.6v-3.3q0-0.3 0.2-0.6 0.2-0.2 0.6-0.2zm8.3 6.7q0.3 0 0.6 0.2t0.2 0.6v3.3q0 0.3-0.2 0.6t-0.6 0.2h-3.3q-0.3 0-0.6-0.2-0.2-0.2-0.2-0.6v-3.3q0-0.3 0.2-0.6 0.2-0.2 0.6-0.2zm4.2 1.7v1.7h-2.9v-1.7zm0-6.7v1.7h-11.2v-1.7z" stroke-width="0"/>
+        </svg>
+      } @else {
+        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="12.6" version="1.1" viewBox="0 0 20 12.6">
+          <path d="m18.5 6.3q-1.7-2.6-4.2-4 0.6 1.1 0.6 2.5 0 2.1-1.5 3.5-1.5 1.5-3.5 1.5-2.1 0-3.5-1.5-1.5-1.5-1.5-3.5 0-1.4 0.6-2.5-2.5 1.2-4.2 4 1.5 2.2 3.7 3.6 2.2 1.4 4.8 1.4 2.6 0 4.8-1.4 2.2-1.4 3.7-3.6zm-7.9-4.2q0-0.2-0.1-0.4t-0.4-0.1q-1.4 0-2.3 1-1 1-1 2.3 0 0.2 0.1 0.4 0.1 0.1 0.4 0.1t0.4-0.1q0.1-0.1 0.1-0.4 0-1 0.6-1.6 0.6-0.6 1.6-0.6 0.2 0 0.4-0.1 0.1-0.1 0.1-0.4zm9.4 4.2q0 0.4-0.2 0.7-1.5 2.6-4.2 4.1-2.6 1.5-5.6 1.5-2.8 0-5.6-1.5-2.6-1.5-4.2-4.1-0.2-0.4-0.2-0.7t0.2-0.7q1.6-2.5 4.2-4.1 2.6-1.5 5.6-1.5 2.8 0 5.6 1.5 2.6 1.5 4.2 4.1 0.2 0.4 0.2 0.7z" stroke-width="0"/>
+        </svg>
+      }

Additionally, add an accessible label to the button:

<button class="bt-view-editor" type="button" (click)="togglePreview()" [attr.aria-label]="isPreviewActive ? 'Exit preview' : 'Enter preview'">
editor/src/app/profile-dropdown/profile-dropdown.component.ts (1)

17-58: Cache the user with @let and use @else; add rel for external link.

This avoids multiple async subscriptions and improves security for target="_blank".

-      @if (!(user$ | async).profileUrl) {
+      @let user = (user$ | async);
+      @if (!user?.profileUrl) {
         <li>
           <a
             [routerLink]="['/account']"
             [routerLinkActive]="'nav-active'"
             queryParamsHandling="preserve"
             >
             Account
             <svg
               xmlns="http://www.w3.org/2000/svg"
               width="13.3"
               height="16"
               version="1.1"
               viewBox="0 0 13.3 16"
               >
               <path
                 d="m13.3 13.2q0 1.1-0.7 1.9t-1.6 0.8h-8.9q-0.9 0-1.6-0.8t-0.7-1.9q0-0.9 0.1-1.7 0.1-0.8 0.3-1.6 0.2-0.8 0.6-1.4t1-0.9q0.6-0.4 1.4-0.4 1.4 1.3 3.3 1.3 1.9 0 3.3-1.3 0.8 0 1.4 0.4 0.6 0.4 1 0.9t0.6 1.4q0.2 0.8 0.3 1.6 0.1 0.8 0.1 1.7zm-2.7-9.2q0 1.7-1.2 2.8-1.2 1.2-2.8 1.2t-2.8-1.2q-1.2-1.2-1.2-2.8 0-1.7 1.2-2.8 1.2-1.2 2.8-1.2t2.8 1.2q1.2 1.2 1.2 2.8z"
                 stroke-width="0"
                 />
             </svg>
           </a>
         </li>
-      }
-      @if ((user$ | async).profileUrl) {
+      } @else {
         <li>
-          <a href="{{ (user$ | async).profileUrl }}" target="_blank">
+          <a [href]="user?.profileUrl" target="_blank" rel="noopener noreferrer">
             Account
             <svg
               xmlns="http://www.w3.org/2000/svg"
               width="13.3"
               height="16"
               version="1.1"
               viewBox="0 0 13.3 16"
               >
               <path
                 d="m13.3 13.2q0 1.1-0.7 1.9t-1.6 0.8h-8.9q-0.9 0-1.6-0.8t-0.7-1.9q0-0.9 0.1-1.7 0.1-0.8 0.3-1.6 0.2-0.8 0.6-1.4t1-0.9q0.6-0.4 1.4-0.4 1.4 1.3 3.3 1.3 1.9 0 3.3-1.3 0.8 0 1.4 0.4 0.6 0.4 1 0.9t0.6 1.4q0.2 0.8 0.3 1.6 0.1 0.8 0.1 1.7zm-2.7-9.2q0 1.7-1.2 2.8-1.2 1.2-2.8 1.2t-2.8-1.2q-1.2-1.2-1.2-2.8 0-1.7 1.2-2.8 1.2-1.2 2.8-1.2t2.8 1.2q1.2 1.2 1.2 2.8z"
                 stroke-width="0"
                 />
             </svg>
           </a>
         </li>
-      }
+      }

Optional: reuse the same user for the button label to avoid another async subscription: {{ user?.name }}.

editor/src/app/sites/shared/setting.component.ts (1)

152-158: Confirm HTML trust boundary for description.

bypassSecurityTrustHtml disables sanitization. Ensure config.description is trusted or pre-sanitized server-side to avoid XSS.

Consider centralizing description rendering behind a utility that sanitizes/whitelists allowed tags.

editor/src/app/sites/template-settings/site-template-settings.component.ts (1)

26-79: Track by stable slugs and improve header link semantics.

  • Use slug-based tracking to minimize DOM churn.
  • Wrap the clickable title in an anchor for native link semantics (keyboard + a11y), rather than role="link" on h3.
-    @for (settingGroup of templateSettings$ | async; track settingGroup) {
+    @for (settingGroup of templateSettings$ | async; track settingGroup.slug) {
       <div
         class="setting-group"
         [class.is-expanded]="camelifySlug(currentGroup) === settingGroup.slug"
         >
-        <h3
-        [routerLink]="[
-          '/design',
-          camelifySlug(currentGroup) === settingGroup.slug
-            ? ''
-            : slugifyCamel(settingGroup.slug)
-        ]"
-          queryParamsHandling="preserve"
-          role="link"
-          class="hoverable"
-          >
-          {{ settingGroup.config.title || settingGroup.slug }}
-          <svg
+        <h3 class="hoverable">
+          <a
+            [routerLink]="[
+              '/design',
+              camelifySlug(currentGroup) === settingGroup.slug
+                ? ''
+                : slugifyCamel(settingGroup.slug)
+            ]"
+            queryParamsHandling="preserve"
+          >
+            {{ settingGroup.config.title || settingGroup.slug }}
+          </a>
+          <svg
             class="drop-icon"
             width="10"
             height="6"
             viewBox="0 0 10 6"
             fill="none"
             xmlns="http://www.w3.org/2000/svg"
             >
             <path
               d="M9 1L4.75736 5.24264L0.514719 1"
               stroke="#9b9b9b"
               stroke-linecap="round"
               stroke-linejoin="round"
               />
           </svg>
         </h3>
         <div
           class="settings"
           [@isExpanded]="camelifySlug(currentGroup) === settingGroup.slug"
           >
-          @for (setting of settingGroup.settings; track setting) {
+          @for (setting of settingGroup.settings; track setting.setting.slug) {
             <berta-setting
               [templateSlug]="settingGroup.templateSlug"
               [setting]="setting.setting"
               [config]="setting.config"
               [disabled]="
                 settingUpdate[settingGroup.slug + ':' + setting.setting.slug]
               "
               [error]="settingError[settingGroup.slug + ':' + setting.setting.slug]"
               (update)="updateSetting(settingGroup.slug, $event)"
               (emitAction)="emitAction($event)"
             ></berta-setting>
           }
         </div>
       </div>
     }
editor/src/app/shop/shop.component.ts (1)

14-18: Track by stable key to prevent unnecessary re-renders

Track by slug instead of object identity to keep DOM stable across emissions.

-    @for (shopSection of shopSections$ | async; track shopSection) {
+    @for (shopSection of shopSections$ | async; track shopSection.slug) {
editor/src/app/login/login.component.ts (1)

25-26: Add alt text to logo for accessibility

-        <h3><img src="/engine/layout/berta.png" /></h3>
+        <h3><img src="/engine/layout/berta.png" alt="Berta CMS" /></h3>
editor/src/app/sites/settings/site-settings.component.ts (1)

32-36: Use stable track keys for @for loops

Track by unique slugs or indices to avoid churn on new object emissions.

-    @for (settingGroup of settings$ | async; track settingGroup) {
+    @for (settingGroup of settings$ | async; track settingGroup.slug) {
...
-          @for (setting of settingGroup.settings; track setting) {
+          @for (setting of settingGroup.settings; track setting.setting.slug) {
...
-                  @for (inputFields of setting.children; track inputFields; let index = $index) {
+                  @for (inputFields of setting.children; let index = $index; track index) {

Also applies to: 69-72, 97-113

editor/src/app/header/header.component.ts (1)

62-64: Add rel to external link opened in new tab

-                          <a href="https://support.berta.me" target="_blank">Knowledge base</a>
+                          <a href="https://support.berta.me" target="_blank" rel="noopener noreferrer">Knowledge base</a>
editor/src/app/sites/sections/background-gallery-editor.component.ts (5)

35-56: Add keyboard/a11y semantics to the file settings toggle

Use role, tabindex, aria-expanded and key handlers so the toggle is operable via keyboard and announced by AT.

Apply:

-            <h3
-              (click)="fileSettingsIsOpen = !fileSettingsIsOpen"
-              class="hoverable"
-              >
+            <h3
+              (click)="fileSettingsIsOpen = !fileSettingsIsOpen"
+              (keydown.enter)="fileSettingsIsOpen = !fileSettingsIsOpen"
+              (keydown.space)="fileSettingsIsOpen = !fileSettingsIsOpen"
+              class="hoverable"
+              role="button"
+              tabindex="0"
+              [attr.aria-expanded]="fileSettingsIsOpen"
+              aria-controls="file-settings"
+              >
@@
-            <div class="settings" [@isExpanded]="fileSettingsIsOpen">
+            <div class="settings" [@isExpanded]="fileSettingsIsOpen" id="file-settings">

73-95: Do the same a11y treatment for the gallery settings toggle

Mirror the accessibility improvements for the gallery settings section.

-          <h3
-            (click)="gallerySettingsIsOpen = !gallerySettingsIsOpen"
-            class="hoverable"
-            >
+          <h3
+            (click)="gallerySettingsIsOpen = !gallerySettingsIsOpen"
+            (keydown.enter)="gallerySettingsIsOpen = !gallerySettingsIsOpen"
+            (keydown.space)="gallerySettingsIsOpen = !gallerySettingsIsOpen"
+            class="hoverable"
+            role="button"
+            tabindex="0"
+            [attr.aria-expanded]="gallerySettingsIsOpen"
+            aria-controls="gallery-settings"
+            >
@@
-          <div class="settings" [@isExpanded]="gallerySettingsIsOpen">
+          <div class="settings" [@isExpanded]="gallerySettingsIsOpen" id="gallery-settings">

284-286: Avoid function call in @for track; use a stable expression

Calling a method per item increases CD cost. Track directly by a unique field.

-          @for (
-            file of currentSection.mediaCacheData?.file; track identifyGalleryItem($index,
-            file)) {
+          @for (file of currentSection.mediaCacheData?.file; track file['@attributes'].src) {

Additionally, remove the now-unused identifyGalleryItem method:

// Remove if unused elsewhere
// identifyGalleryItem(_, item: SiteSectionBackgroundFile) { return item['@attributes'].src; }

296-301: Prefer property binding for img src and add alt/lazy hints

Use [src] to avoid double interpolation, add alt, and opt-in to lazy decoding.

-                <img
-                  draggable="false"
-                  src="{{ currentSite.mediaUrl }}/{{ currentSection.mediafolder }}/_smallthumb_{{ file['@attributes'].src }}"
-                  />
+                <img
+                  draggable="false"
+                  [src]="currentSite.mediaUrl + '/' + currentSection.mediafolder + '/_smallthumb_' + file['@attributes'].src"
+                  [attr.alt]="file['@attributes'].src"
+                  loading="lazy"
+                  decoding="async"
+                />

275-281: Optional: collapse duplicate @if (currentSection) blocks

You can wrap aside and content in a single @if to reduce duplication.

Example:

@if (currentSection) {
  <aside>...</aside>
  <div class="content">...</div>
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5738988 and 83e8390.

⛔ Files ignored due to path filters (1)
  • editor/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (38)
  • editor/angular.json (1 hunks)
  • editor/package.json (1 hunks)
  • editor/src/app/header/header.component.ts (1 hunks)
  • editor/src/app/header/preview-toggle.component.ts (1 hunks)
  • editor/src/app/inputs/file-input.component.ts (1 hunks)
  • editor/src/app/inputs/files-input.component.ts (1 hunks)
  • editor/src/app/inputs/inline-text-input.component.ts (1 hunks)
  • editor/src/app/inputs/select-input.component.ts (3 hunks)
  • editor/src/app/inputs/text-input.component.ts (1 hunks)
  • editor/src/app/inputs/url-input.component.ts (1 hunks)
  • editor/src/app/login/login.component.ts (1 hunks)
  • editor/src/app/popup/popup.component.ts (1 hunks)
  • editor/src/app/profile-dropdown/profile-dropdown.component.ts (2 hunks)
  • editor/src/app/shared/interfaces.ts (1 hunks)
  • editor/src/app/shop/orders/shop-orders.component.ts (1 hunks)
  • editor/src/app/shop/products/shop-products.component.ts (1 hunks)
  • editor/src/app/shop/regional-costs/shop-regional-costs.component.ts (2 hunks)
  • editor/src/app/shop/settings/shop-settings.component.ts (1 hunks)
  • editor/src/app/shop/shop.component.ts (1 hunks)
  • editor/src/app/sites/media/entry-gallery-editor.component.ts (13 hunks)
  • editor/src/app/sites/media/entry-gallery-image-editor.component.ts (6 hunks)
  • editor/src/app/sites/media/entry-gallery.component.ts (2 hunks)
  • editor/src/app/sites/media/site-media.component.ts (5 hunks)
  • editor/src/app/sites/sections/background-gallery-editor.component.ts (9 hunks)
  • editor/src/app/sites/sections/section.component.ts (2 hunks)
  • editor/src/app/sites/sections/site-sections.component.ts (1 hunks)
  • editor/src/app/sites/settings/site-settings.component.ts (3 hunks)
  • editor/src/app/sites/shared/help-tooltip.component.ts (2 hunks)
  • editor/src/app/sites/shared/setting-row-add.component.ts (1 hunks)
  • editor/src/app/sites/shared/setting-row.component.ts (1 hunks)
  • editor/src/app/sites/shared/setting.component.ts (1 hunks)
  • editor/src/app/sites/shared/sites-shared.module.ts (2 hunks)
  • editor/src/app/sites/site.component.ts (3 hunks)
  • editor/src/app/sites/sites.component.ts (1 hunks)
  • editor/src/app/sites/template-settings/site-template-settings.component.ts (1 hunks)
  • editor/src/app/themes/themes.component.ts (1 hunks)
  • editor/src/app/user/user-account.component.ts (4 hunks)
  • editor/tsconfig.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
editor/src/app/sites/media/site-media.component.ts (1)
editor/src/app/sites/sections/sections-state/site-sections-state.model.ts (1)
  • SiteSectionStateModel (1-37)
🔇 Additional comments (9)
editor/src/app/inputs/url-input.component.ts (1)

7-73: LGTM! Clean migration to Angular v17+ control flow syntax.

The template has been successfully migrated from *ngIf to @if blocks while preserving all logic, bindings, and accessibility attributes. The conditional rendering for the icon (textInputService.showIcon | async) and clear button (allowBlank && value) remains functionally equivalent.

editor/angular.json (1)

125-150: Workspace-level schematics defaults look good

Settings align with Angular CLI conventions and complement project-level overrides.

editor/package.json (2)

35-35: New tooltip dependency looks good; ensure module/directive is imported where used.

Confirm consumers import either NgxTooltipDirectivesModule (NgModule apps) or the specific TooltipStrDirective in standalone components. Consider setting global defaults via forRoot for consistent styling.

Based on learnings


14-14: Angular 20.3 supports Node.js 24
Angular v20.3 (CLI and build-angular) officially supports Node.js 24, so the engines entry is correct.

editor/src/app/sites/shared/help-tooltip.component.ts (1)

6-13: Tooltip directive inputs validated

textColor, backgroundColor, borderColor and showDelay are valid TooltipOptions inputs in ngx-tooltip-directives v20.2.x; current usage is correct.

editor/src/app/sites/shared/sites-shared.module.ts (1)

25-25: Approve tooltip module swap: migration is complete—no leftover @amin-karimi/ng2-tooltip-directive imports or bare tooltip attributes detected.

editor/src/app/sites/shared/setting.component.ts (1)

12-150: Solid migration to @switch/@case; bindings preserved.

Cases map cleanly to prior inputs; event wiring and props look consistent.

editor/src/app/sites/settings/site-settings.component.ts (1)

123-127: Confirm HTML source for getSettingDescription

Bypassing security assumes trusted source. If config can be user-controlled, sanitize upstream.

Do setting descriptions originate only from trusted, static config? If not, we should sanitize or restrict allowed tags.

editor/src/app/sites/sections/background-gallery-editor.component.ts (1)

28-75: Good migration to @if blocks

Template conversion to Angular control flow reads cleanly. No functional regressions spotted in this section.

Comment thread editor/src/app/header/header.component.ts
Comment thread editor/src/app/inputs/file-input.component.ts
Comment on lines +8 to +9
[class.bt-disabled]="disabled"
>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Disable coercion + null-safe files check

disabled is used as a boolean but can be passed as a bare attribute (string), causing the control to be permanently disabled. Also guard against a null FileList.

Apply this diff for the null-safe check:

-    if (this.disabled || !fileInput.files.length) {
+    if (this.disabled || !fileInput.files || fileInput.files.length === 0) {
       return;
     }

And update the input to coerce booleans (outside these lines):

// add to imports
import { booleanAttribute } from '@angular/core';

// replace the input declaration
@Input({ transform: booleanAttribute }) disabled = false;

Also applies to: 16-16, 68-68

🤖 Prompt for AI Agents
In editor/src/app/inputs/files-input.component.ts around lines 8-9 (also applies
to lines 16 and 68), the template and component treat "disabled" as a plain
boolean but it may be passed as a bare string attribute, and FileList checks are
not null-safe; import booleanAttribute from @angular/core and change the input
declaration to use transform: booleanAttribute (i.e. add the import and replace
the @Input with @Input({ transform: booleanAttribute }) disabled = false), and
update any file-list checks in the template and component to guard against
null/undefined (e.g., check files?.length or files && files.length) so the
control isn’t permanently disabled and null FileList won’t throw.

Comment thread editor/src/app/inputs/files-input.component.ts
Comment thread editor/src/app/inputs/select-input.component.ts
Comment thread editor/src/app/sites/media/site-media.component.ts
Comment on lines +290 to +293
ngSortgridItem
[ngSortGridItems]="currentSection.mediaCacheData.file"
(sorted)="reorder($event)"
(click)="setSelectedFile(file)"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Double-check ng-sortgrid directive selector casing

Attribute is written as ngSortgridItem while the input is [ngSortGridItems]. Ensure the directive selector matches the library’s expected casing to avoid a no-op.

Run to verify usage consistency across the repo:


🏁 Script executed:

#!/bin/bash
# Show all occurrences of the directive attribute to compare casing
rg -n -C2 -P '\bngSortgridItem\b|\bngSortGridItem\b|\[ngSortGridItems\]'

Length of output: 1532


Fix directive attribute casing for ngSortGridItem
Replace ngSortgridItem with ngSortGridItem in:

  • editor/src/app/sites/media/entry-gallery-editor.component.ts (line 361)
  • editor/src/app/sites/sections/background-gallery-editor.component.ts (line 290)
    to match the library’s expected selector and ensure the directive applies.
🤖 Prompt for AI Agents
editor/src/app/sites/sections/background-gallery-editor.component.ts lines
290-293 and editor/src/app/sites/media/entry-gallery-editor.component.ts around
line 361: the directive attribute is incorrectly cased as ngSortgridItem; update
the attribute to ngSortGridItem in both templates so it matches the library
selector and the directive will be applied; search the repo for any other
occurrences of "ngSortgridItem" and replace them with "ngSortGridItem" to ensure
consistency.

Comment thread editor/src/app/sites/settings/site-settings.component.ts
Comment thread editor/src/app/sites/shared/setting-row-add.component.ts
Comment thread editor/src/app/sites/site.component.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
editor/src/app/sentry/sentry-config.service.ts (1)

11-20: Trim DSN to avoid stray whitespace issues

Safe micro-improvement; prevents falsey checks failing due to spaces/newlines.

-      return dsn;
+      return dsn.trim();
editor/src/app/sentry/sentry-init.factory.ts (1)

12-13: Consider wiring tracing and release/environment

Optional: add tracing and metadata for better observability.

+import { environment } from '../environments/environment';
@@
-    Sentry.init({ dsn });
+    Sentry.init({
+      dsn,
+      // Enable browser tracing (adjust sample rate as needed)
+      integrations: [Sentry.browserTracingIntegration()],
+      tracesSampleRate: environment.production ? 0.1 : 1.0,
+      environment: environment.production ? 'production' : 'development',
+      // Set release if available (e.g., injected at build time)
+      release: (window as any).SENTRY_RELEASE ?? undefined,
+    });
editor/package.json (1)

30-32: Move @sentry/cli to devDependencies

It’s a build-time tool; keeping it in dependencies bloats install surface unnecessarily.

   "@sentry/angular": "^10.17.0",
-  "@sentry/cli": "^2.56.0",
@@
   "typescript": "~5.8.3"
+  ,
+  "@sentry/cli": "^2.56.0"

Also applies to: 45-69

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83e8390 and 205c56b.

⛔ Files ignored due to path filters (1)
  • editor/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • editor/.gitignore (1 hunks)
  • editor/angular.json (2 hunks)
  • editor/package.json (1 hunks)
  • editor/src/app/app.module.ts (3 hunks)
  • editor/src/app/sentry.error-handler.ts (0 hunks)
  • editor/src/app/sentry/sentry-config.service.ts (1 hunks)
  • editor/src/app/sentry/sentry-init.factory.ts (1 hunks)
  • editor/src/app/sites/sections/site-sections.component.ts (3 hunks)
💤 Files with no reviewable changes (1)
  • editor/src/app/sentry.error-handler.ts
✅ Files skipped from review due to trivial changes (1)
  • editor/.gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
  • editor/angular.json
  • editor/src/app/sites/sections/site-sections.component.ts
🧰 Additional context used
🧬 Code graph analysis (1)
editor/src/app/app.module.ts (1)
editor/src/app/sentry/sentry-init.factory.ts (1)
  • sentryInitFactory (4-14)
🔇 Additional comments (2)
editor/package.json (2)

15-16: Version compatibility confirmed: Node (^20.19.0‖^22.12.0‖^24.0.0), Angular core/CLI/build-angular (20.3.x), TypeScript (≥5.8 <6.0), and RxJS (7.8.x) are all mutually supported.


12-12: Confirm sourcemaps paths: Unable to locate an explicit outputPath or existing dist directory—please verify that ../engine/dist matches your build output and adjust the script accordingly.

Comment thread editor/package.json Outdated
Comment thread editor/src/app/app.module.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 205c56b and a36cff8.

⛔ Files ignored due to path filters (1)
  • _api_app/composer.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • _api_app/.env.example (1 hunks)
  • _api_app/app/Http/Controllers/StateController.php (1 hunks)
  • _api_app/composer.json (1 hunks)
  • _api_app/config/sentry.php (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • _api_app/.env.example
🧰 Additional context used
📓 Path-based instructions (12)
_api_app/**/*.php

📄 CodeRabbit inference engine (_api_app/CLAUDE.md)

_api_app/**/*.php: Always use curly braces for control structures, even for single-line statements.
Use PHP 8 constructor property promotion in __construct() methods.
Do not allow empty __construct() methods with zero parameters.
Always use explicit return type declarations for methods and functions.
Use appropriate PHP type hints for method parameters.
Prefer PHPDoc blocks over inline comments. Avoid comments within code unless the logic is very complex.
Add useful array shape type definitions in PHPDoc blocks when working with arrays.
Enum keys should be TitleCase, for example: FavoritePerson, BestLake, Monthly.
Use php artisan make: commands to create new Laravel files. Use artisan make:class for generic PHP classes.
Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).
When generating links, prefer named routes and the route() function.

_api_app/**/*.php: Always use curly braces for PHP control structures, even for single-line bodies
Use PHP 8 constructor property promotion in __construct() and avoid empty constructors
Always use explicit return type declarations for methods and functions
Use appropriate PHP type hints for method parameters
Prefer PHPDoc blocks over inline code comments; only add inline comments for very complex logic
Add useful PHPDoc array shape type definitions where appropriate
Enum case names should be TitleCase (e.g., FavoritePerson, BestLake, Monthly)
Prefer Eloquent models and relationships over raw database queries (avoid DB:: where possible; use Model::query())
Use Faker via $this->faker or fake() consistently with existing conventions in tests and factories

Files:

  • _api_app/config/sentry.php
  • _api_app/app/Http/Controllers/StateController.php
_api_app/**/config/*.php

📄 CodeRabbit inference engine (_api_app/CLAUDE.md)

Use environment variables (env()) only in configuration files. Elsewhere, use config() function.

Files:

  • _api_app/config/sentry.php
_api_app/config/**/*.php

📄 CodeRabbit inference engine (_api_app/.cursor/rules/laravel-boost.mdc)

Use env() only within configuration files

Access environment variables via env() within configuration files

Files:

  • _api_app/config/sentry.php
_api_app/{app,bootstrap,config,routes,database,tests}/**/*.php

📄 CodeRabbit inference engine (_api_app/AGENTS.md)

_api_app/{app,bootstrap,config,routes,database,tests}/**/*.php: Always use curly braces for control structures in PHP, even for single-line bodies
Use PHP 8 constructor property promotion in __construct() and do not include empty constructors
Always declare explicit return types and parameter type hints for functions and methods
Prefer PHPDoc blocks over inline comments; only comment complex logic and add useful array shape types when appropriate

Files:

  • _api_app/config/sentry.php
  • _api_app/app/Http/Controllers/StateController.php
_api_app/**/app/Http/Controllers/*.php

📄 CodeRabbit inference engine (_api_app/CLAUDE.md)

Always create Form Request classes for validation rather than inline validation in controllers.

Files:

  • _api_app/app/Http/Controllers/StateController.php
_api_app/{app/Http/Controllers/**/*.php,app/Models/**/*.php}

📄 CodeRabbit inference engine (_api_app/.cursor/rules/laravel-boost.mdc)

Prevent N+1 queries by using eager loading when accessing relationships

Files:

  • _api_app/app/Http/Controllers/StateController.php
_api_app/{resources/views/**/*.blade.php,app/Http/Controllers/**/*.php}

📄 CodeRabbit inference engine (_api_app/.cursor/rules/laravel-boost.mdc)

Generate links using named routes and the route() helper

Files:

  • _api_app/app/Http/Controllers/StateController.php
_api_app/{app/**/*.php,bootstrap/**/*.php,routes/**/*.php,database/**/*.php}

📄 CodeRabbit inference engine (_api_app/.cursor/rules/laravel-boost.mdc)

Do not use env() outside configuration files; use config('...') instead

Files:

  • _api_app/app/Http/Controllers/StateController.php
_api_app/app/Http/Controllers/**/*.php

📄 CodeRabbit inference engine (_api_app/.cursor/rules/laravel-boost.mdc)

Do not perform inline validation in controllers; use dedicated Form Request classes

Use Form Request classes for validation instead of inline validation in controllers; include rules and custom messages

Files:

  • _api_app/app/Http/Controllers/StateController.php
_api_app/app/**/*.php

📄 CodeRabbit inference engine (_api_app/AGENTS.md)

Enum case names should be TitleCase (e.g., FavoritePerson, Monthly)

Files:

  • _api_app/app/Http/Controllers/StateController.php
_api_app/{app,bootstrap,routes,database,tests}/**/*.php

📄 CodeRabbit inference engine (_api_app/AGENTS.md)

_api_app/{app,bootstrap,routes,database,tests}/**/*.php: Avoid DB:: facade; prefer Model::query() and Eloquent ORM features
Never use env() outside config files; use config('app.name') etc. instead

Files:

  • _api_app/app/Http/Controllers/StateController.php
_api_app/{resources/views/**/*.blade.php,app/Http/Controllers/**/*.php,routes/**/*.php}

📄 CodeRabbit inference engine (_api_app/AGENTS.md)

Prefer named routes and route() for URL generation

Files:

  • _api_app/app/Http/Controllers/StateController.php
🔇 Additional comments (2)
_api_app/config/sentry.php (2)

26-26: No changes needed for Sentry documentation URL anchors. The underscore-separated fragments match the official docs.


35-37: Confirm sentry-laravel ≥ v4.15.0 for enable_logs support
The enable_logs option was introduced in sentry-php/sentry-laravel v4.15.0 and defaults to false. Ensure your project’s Sentry Laravel SDK dependency is updated to at least v4.15.0 to activate the logging integration.

Comment thread _api_app/app/Http/Controllers/StateController.php
Comment thread _api_app/composer.json
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
editor/package.json (1)

29-31: Move @sentry/cli to devDependencies.

@sentry/cli is a build-time tool; keeping it in dependencies forces it into production installs and containers, bloating the footprint. Shift it under devDependencies alongside the rest of the tooling.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a36cff8 and 064db73.

⛔ Files ignored due to path filters (2)
  • _api_app/composer.lock is excluded by !**/*.lock
  • editor/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • editor/README.md (0 hunks)
  • editor/angular.json (2 hunks)
  • editor/e2e/protractor.conf.js (0 hunks)
  • editor/e2e/src/app.e2e-spec.ts (0 hunks)
  • editor/e2e/src/app.po.ts (0 hunks)
  • editor/e2e/tsconfig.e2e.json (0 hunks)
  • editor/package.json (1 hunks)
💤 Files with no reviewable changes (5)
  • editor/e2e/tsconfig.e2e.json
  • editor/README.md
  • editor/e2e/src/app.po.ts
  • editor/e2e/src/app.e2e-spec.ts
  • editor/e2e/protractor.conf.js

Comment thread editor/angular.json
Comment thread editor/package.json Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
editor/src/app/sites/media/entry-gallery.component.ts (1)

65-66: Remove unused properties.

The site and mediaUrl properties are computed but never referenced in the template, which uses currentSite.mediaUrl directly instead. These properties appear to be dead code.

Apply this diff to remove the unused code:

-  site = this.store.selectSnapshot(AppState.getSite);
-  mediaUrl = `/storage/${this.site ? `-sites/${this.site}/` : ''}media`;
-
   constructor(private store: Store) {}
♻️ Duplicate comments (1)
editor/package.json (1)

11-11: sentry-cli upload will fail without an explicit release

Pass a release to both inject and upload; otherwise sentry-cli exits with an error. Use the package version.

-    "sentry:sourcemaps": "npm run build && sentry-cli sourcemaps inject --org hello-world-ik --project berta-frontend ../engine/dist && sentry-cli sourcemaps upload --org hello-world-ik --project berta-frontend ../engine/dist"
+    "sentry:sourcemaps": "npm run build && sentry-cli sourcemaps inject --org hello-world-ik --project berta-frontend --release $npm_package_version ../engine/dist && sentry-cli sourcemaps upload --org hello-world-ik --project berta-frontend --release $npm_package_version ../engine/dist"
🧹 Nitpick comments (3)
editor/package.json (1)

52-52: Remove leftover Protractor typings

@types/jasminewd2 is for Protractor/WebDriverJS. With Protractor removed, this type package should be dropped.

-    "@types/jasminewd2": "~2.0.3",
editor/src/app/sites/media/entry-gallery.component.ts (2)

20-49: Consider using @else if or @switch for mutually exclusive conditions.

The type checking for image vs. video uses separate @if blocks (lines 20 and 28), but these conditions are mutually exclusive—a file can't be both an image and a video. Using @else if or a @switch block would make this logic clearer and slightly more efficient.

Option 1: Use @else if

           @if (file['@attributes'].type === 'image') {
           <div class="media image">
             <img
               src="{{ currentSite.mediaUrl }}/{{
                 entry.mediafolder
               }}/_smallthumb_{{ file['@attributes'].src }}"
             />
           </div>
-          } @if (file['@attributes'].type === 'video') {
+          } @else if (file['@attributes'].type === 'video') {
           <div class="media video">
             <video
               [attr.poster]="
                 file['@attributes'].poster_frame
                   ? currentSite.mediaUrl +
                     '/' +
                     entry.mediafolder +
                     '/' +
                     file['@attributes'].poster_frame
                   : null
               "
             >
               <source
                 src="{{ currentSite.mediaUrl }}/{{ entry.mediafolder }}/{{
                   file['@attributes'].src
                 }}"
                 type="video/mp4"
               />
             </video>
           </div>
           }

Option 2: Use @switch

-          @if (file['@attributes'].type === 'image') {
+          @switch (file['@attributes'].type) {
+          @case ('image') {
           <div class="media image">
             <img
               src="{{ currentSite.mediaUrl }}/{{
                 entry.mediafolder
               }}/_smallthumb_{{ file['@attributes'].src }}"
             />
           </div>
-          } @if (file['@attributes'].type === 'video') {
+          }
+          @case ('video') {
           <div class="media video">
             <video
               [attr.poster]="
                 file['@attributes'].poster_frame
                   ? currentSite.mediaUrl +
                     '/' +
                     entry.mediafolder +
                     '/' +
                     file['@attributes'].poster_frame
                   : null
               "
             >
               <source
                 src="{{ currentSite.mediaUrl }}/{{ entry.mediafolder }}/{{
                   file['@attributes'].src
                 }}"
                 type="video/mp4"
               />
             </video>
           </div>
           }
+          }

18-18: Consider a more specific track expression.

The @for loop tracks by file object reference, which works if file objects are immutable. However, if files have a unique identifier (e.g., file['@attributes'].src or an ID), tracking by that property would be more robust and improve change detection performance.

For example:

-        @for (file of entry.mediaCacheData.file; track file) {
+        @for (file of entry.mediaCacheData.file; track file['@attributes'].src) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 064db73 and df79986.

⛔ Files ignored due to path filters (1)
  • editor/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • editor/package.json (1 hunks)
  • editor/src/app/sites/media/entry-gallery.component.ts (2 hunks)
🔇 Additional comments (3)
editor/package.json (2)

66-66: No changes needed: TypeScript ~5.8.3 satisfies Angular v20’s minimum TS 5.8 requirement.


14-14: No changes needed for Node 24 Angular v20 officially supports Node.js ^24.0.0 (alongside ^20.19.0 and ^22.12.0), so the engines entry is correct.

editor/src/app/sites/media/entry-gallery.component.ts (1)

23-66: Remove unnecessary null check for currentSite.

The parent site-media.component.ts already guards with @if (currentSite$ | async) { … <entry-gallery [currentSite]="currentSite$ | async"> … }, so currentSite in EntryGalleryComponent is never undefined at runtime.

Likely an incorrect or invalid review comment.

Comment thread editor/package.json Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
editor/src/app/sites/media/entry-gallery.component.ts (1)

26-47: Use @else if instead of standalone @if for video block.

Line 26 uses a standalone @if condition instead of @else if, which breaks the mutually exclusive logic between image and video rendering. If file['@attributes'].type is neither 'image' nor 'video', neither block will render. While this might be the intended fallback behavior, the pattern suggests these should be mutually exclusive branches.

Apply this diff to ensure mutual exclusivity:

         }
-        @if (file['@attributes'].type === 'video') {
+        @else if (file['@attributes'].type === 'video') {
         <div class="media video">
editor/package.json (1)

54-65: Resolve the TypeScript 5.8 vs TSLint/Codelyzer peer conflict

tslint@6.1.0 and codelyzer@6.0.2 both cap their TypeScript peer dependency at <5.0.0. Upgrading typescript to ~5.8.3 makes npm install fail with an ERESOLVE (or forces --legacy-peer-deps), so the project can’t install or build in its default workflow. Please drop these obsolete packages (or migrate to ESLint) before landing the TypeScript bump.

Minimal fix:

-    "codelyzer": "^6.0.2",
...
-    "tslint": "~6.1.0",
-    "typescript": "~5.8.3"
+    "typescript": "~5.8.3"

(Or replace with the modern ESLint tooling if linting is still required.)

🧹 Nitpick comments (1)
editor/src/app/sites/media/entry-gallery.component.ts (1)

16-16: Consider tracking by unique identifier instead of object reference.

The track file expression tracks by object reference, which can cause unnecessary re-renders if the file objects are recreated with new references. Consider tracking by a unique identifier for better performance.

Apply this diff to track by the file source:

-      @for (file of entry.mediaCacheData.file; track file) {
+      @for (file of entry.mediaCacheData.file; track file['@attributes'].src) {
       <div class="entry-gallery-item">
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df79986 and 45b381a.

⛔ Files ignored due to path filters (1)
  • editor/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • editor/package.json (1 hunks)
  • editor/src/app/sites/media/entry-gallery.component.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
editor/src/app/sites/media/entry-gallery.component.ts (1)
editor/src/app/sites/media/site-media.component.ts (1)
  • Component (17-228)
🔇 Additional comments (2)
editor/src/app/sites/media/entry-gallery.component.ts (2)

1-4: LGTM! Store dependency successfully removed.

The imports correctly reflect the migration away from NGXS store, keeping only the essential Angular core imports and model types.


59-62: LGTM! Clean component class aligned with store removal.

The component class correctly implements the migration away from store injection, relying solely on @Input properties to receive currentSite and entry data.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
editor/src/app/sites/media/entry-gallery.component.ts (1)

19-48: Use @else if for mutually exclusive conditions.

The two separate @if blocks at lines 19 and 27 evaluate both conditions even when the first matches. Since type should be either 'image' or 'video' (mutually exclusive), chain them with @else if for better performance and clarity.

Apply this diff:

           @if (file['@attributes'].type === 'image') {
           <div class="media image">
             <img
               src="{{ currentSite.mediaUrl }}/{{
                 entry.mediafolder
               }}/_smallthumb_{{ file['@attributes'].src }}"
             />
           </div>
-          } @if (file['@attributes'].type === 'video') {
+          } @else if (file['@attributes'].type === 'video') {
           <div class="media video">
             <video
               [attr.poster]="
                 file['@attributes'].poster_frame
                   ? currentSite.mediaUrl +
                     '/' +
                     entry.mediafolder +
                     '/' +
                     file['@attributes'].poster_frame
                   : null
               "
             >
               <source
                 src="{{ currentSite.mediaUrl }}/{{ entry.mediafolder }}/{{
                   file['@attributes'].src
                 }}"
                 type="video/mp4"
               />
             </video>
           </div>
           }
editor/package.json (3)

14-14: Node engines: confirm Node 24 support and intended Node 18 drop.

Angular 20 toolchain may not be fully validated on Node 24 yet. Also, excluding Node 18 is a policy decision—verify it’s intentional.

If you prefer safer bounds, consider narrowing or including 18 LTS:

-    "node": "^20.19.0 || ^22.12.0 || ^24.0.0"
+    "node": "^20.19.0 || ^22.0.0"
+# or (if you still want to support 18 LTS)
+# "node": "^18.19.0 || ^20.19.0 || ^22.0.0"

62-62: ts-node is too old for TS 5.8/Node 20+.

v9.1.1 predates TS 5.x and Node 20 APIs; it will likely fail if used.

-    "ts-node": "~9.1.1"
+    "ts-node": "^10.9.2"

If you don’t use ts-node anywhere, remove it to reduce risk.


51-61: Modernize test stack for CLI v20.

  • jasmine-core/~@types/jasmine 3.x are quite old; upgrade to Jasmine 5.x.
  • Replace karma-coverage-istanbul-reporter with karma-coverage (the Istanbul reporter is deprecated).
-    "@types/jasmine": "~3.8.0",
-    "jasmine-core": "~3.8.0",
-    "karma-coverage-istanbul-reporter": "~3.0.2",
+    "@types/jasmine": "^5.1.0",
+    "jasmine-core": "^5.2.0",
+    "karma-coverage": "^2.2.0",

Adjust karma.conf.js accordingly (reporters: ['progress','coverage'], etc.).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45b381a and f90369c.

⛔ Files ignored due to path filters (1)
  • editor/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • editor/package.json (1 hunks)
  • editor/src/app/pipes/pipe.ts (1 hunks)
  • editor/src/app/sites/media/entry-gallery.component.ts (2 hunks)
  • editor/src/tslint.json (0 hunks)
  • editor/tslint.json (0 hunks)
💤 Files with no reviewable changes (2)
  • editor/src/tslint.json
  • editor/tslint.json
✅ Files skipped from review due to trivial changes (1)
  • editor/src/app/pipes/pipe.ts
🧰 Additional context used
🧬 Code graph analysis (1)
editor/src/app/sites/media/entry-gallery.component.ts (1)
editor/src/app/sites/media/site-media.component.ts (1)
  • Component (17-228)
🔇 Additional comments (4)
editor/src/app/sites/media/entry-gallery.component.ts (1)

60-62: LGTM: Clean migration to input-based architecture.

The removal of store dependency in favor of direct @Input properties simplifies the component and improves testability. The inputs correctly match the parent component's data flow.

editor/package.json (3)

18-26: Angular v20 dependency alignment looks good.

Versions are consistent across core packages and ecosystem libs for this upgrade.

Also applies to: 28-29, 32-36, 40-40


43-47: No action needed—TS ~5.8.3 falls within Angular 20.3.3’s supported range (>=5.8 <6.0).


11-11: Verify production sourcemaps, outputPath, and release parity.

  • In editor/angular.json under build → configurations → production, enable "sourceMap": true (or the detailed object) to emit .map files.
  • Confirm outputPath.base remains "../engine/dist" (matches your Sentry CLI path).
  • Ensure the runtime Sentry.init release string exactly matches $npm_package_version and that this variable expands on Windows.

Comment thread editor/src/app/sites/media/entry-gallery.component.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f90369c and 10f1d4f.

⛔ Files ignored due to path filters (1)
  • editor/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • editor/package.json (1 hunks)

Comment thread editor/package.json
"dev": "ng build --watch --configuration development",
"test": "ng test",
"e2e": "ng e2e"
"sentry:sourcemaps": "npm run build && sentry-cli sourcemaps inject --org hello-world-ik --project berta-frontend --release $npm_package_version ../engine/dist && sentry-cli sourcemaps upload --org hello-world-ik --project berta-frontend --release $npm_package_version ../engine/dist"
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the Sentry script cross-platform

The sentry:sourcemaps command relies on $npm_package_version, which npm only expands under POSIX shells; on Windows it stays literal and the command fails. Please swap in a shell-agnostic lookup (e.g. --release "$(node -p \"require('./package.json').version\")" or wrap the script with cross-env) so the workflow succeeds on every developer machine.

🤖 Prompt for AI Agents
In editor/package.json around line 11, the sentry:sourcemaps script uses the
POSIX-only $npm_package_version which fails on Windows; update the script to
compute the release value in a cross-platform way — replace $npm_package_version
with a shell-agnostic lookup such as --release "$(node -p
\"require('./package.json').version\")" (or alternatively wrap the script with
cross-env/cross-env-shell to supply npm_package_version) so the sentry-cli
commands receive the correct release value on all platforms.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @coderabbitai no windows support in this project :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@uldisrudzitis, understood! Thanks for clarifying—I'll keep that in mind for future reviews.


✏️ Learnings added
Learnt from: uldisrudzitis
PR: berta-cms/berta#576
File: editor/package.json:11-11
Timestamp: 2025-10-08T08:09:22.727Z
Learning: The Berta project does not support Windows as a platform, so cross-platform compatibility with Windows is not required.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai do I need the internet explorer support? 😀

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for browserslist configuration
echo "=== Checking for .browserslistrc ==="
fd -t f "^\.browserslistrc$" .

echo -e "\n=== Checking package.json for browserslist config ==="
cat editor/package.json | jq -r '.browserslist // "No browserslist config in package.json"'

echo -e "\n=== Checking angular.json for browser target ==="
cat editor/angular.json | jq -r '.projects.berta.architect.build.options.tsConfig // "N/A"' 2>/dev/null || echo "Could not find tsConfig in angular.json"

echo -e "\n=== Searching for IE references in config files ==="
rg -i "internet.?explorer|\\bIE\\b|\\bIE[0-9]+" --type=json -g '!node_modules' -g '!package-lock.json' editor/ || echo "No IE references found"

Length of output: 486


🏁 Script executed:

#!/bin/bash
# Check for hidden .browserslistrc file
echo "=== Checking for hidden .browserslistrc ==="
fd -H -t f "^\.browserslistrc$" editor/ || echo "No .browserslistrc file found"

echo -e "\n=== Angular v20 default browser support info ==="
echo "Angular v20 supports: last 2 Chrome/Edge/Firefox/Safari versions + Chrome/Safari iOS"
echo "Internet Explorer has been unsupported since Angular v13 (2021)"

Length of output: 269


🏁 Script executed:

#!/bin/bash
# Check the contents of .browserslistrc
echo "=== Contents of editor/src/.browserslistrc ==="
cat editor/src/.browserslistrc

Length of output: 420


@uldisrudzitis, nope! 😄 Internet Explorer has been officially retired since June 2022, and Angular dropped IE support way back in v13 (2021). Angular v20 absolutely doesn't support it.

However, I did notice your .browserslistrc file still has this line:

IE 9-11

You should remove that line since it's no longer relevant and Angular v20 can't target IE anyway. The file is at editor/src/.browserslistrc.

Here's what you can keep for modern browser support:

> 0.5%
last 2 versions
Firefox ESR
not dead

This will target current browsers that Angular v20 actually supports (Chrome, Edge, Firefox, Safari). 🚀

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10f1d4f and f34c8e5.

📒 Files selected for processing (1)
  • editor/src/.browserslistrc (1 hunks)

Comment thread editor/src/.browserslistrc Outdated
@uldisrudzitis uldisrudzitis merged commit 0e2da3e into master Oct 8, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file Enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant