The mat-select event handler is incorrect. The valueChange event emits the new value directly, not an event object. Change $event.value to just $event, or use the proper approach by updating the day object and emitting it. The current code will emit undefined.
The mat-select event handler is incorrect. The `valueChange` event emits the new value directly, not an event object. Change `$event.value` to just `$event`, or use the proper approach by updating the day object and emitting it. The current code will emit `undefined`.
```suggestion:-0+0
<mat-select (valueChange)="dayChange.emit($event)" [value]="day().type">
```
Direct mutation of an input signal's value is not allowed and violates Angular's signal immutability principle. Instead, create a new object with the updated property. Replace lines 93-94 with: this.dayChange.emit({ ...this.day(), numberOfDays: value });
this.dayChange.emit({ ...this.day(), numberOfDays: value });
Direct mutation of an input signal's value is not allowed and violates Angular's signal immutability principle. Instead, create a new object with the updated property. Replace lines 93-94 with: `this.dayChange.emit({ ...this.day(), numberOfDays: value });`
```suggestion:-0+0
this.dayChange.emit({ ...this.day(), numberOfDays: value });
```
Direct mutation of an input signal's value is not allowed and violates Angular's signal immutability principle. Instead, create a new object with the updated property. Replace lines 98-99 with: this.dayChange.emit({ ...this.day(), expiryDate: event.value! });
Direct mutation of an input signal's value is not allowed and violates Angular's signal immutability principle. Instead, create a new object with the updated property. Replace lines 98-99 with: `this.dayChange.emit({ ...this.day(), expiryDate: event.value! });`
```suggestion:-0+0
this.dayChange.emit({ ...this.day(), expiryDate: event.value! });
}
```
Directly mutating the array returned from a signal violates signal immutability. Use the signal's update method instead: this.userInputService.vacationNumberSignal.update(days => [...days, this.userInputService.createVacationDay()]);
Directly mutating the array returned from a signal violates signal immutability. Use the signal's `update` method instead: `this.userInputService.vacationNumberSignal.update(days => [...days, this.userInputService.createVacationDay()]);`
```suggestion:-0+0
this.userInputService.vacationNumberSignal.update(days => [...days, this.userInputService.createVacationDay()]);
```
Using any type defeats TypeScript's type safety. The parameter type should match the actual data structure being used. Based on the code, this should be VacationsNumber (with properties cp, rtt, other), not the new VacDay[] type.
Using `any` type defeats TypeScript's type safety. The parameter type should match the actual data structure being used. Based on the code, this should be `VacationsNumber` (with properties cp, rtt, other), not the new `VacDay[]` type.
Using any type defeats TypeScript's type safety. The parameter type should be VacationsNumber (with properties cp, rtt, other) to match how it's used throughout the method.
Using `any` type defeats TypeScript's type safety. The parameter type should be `VacationsNumber` (with properties cp, rtt, other) to match how it's used throughout the method.
Directly mutating the array returned from a signal violates signal immutability. Use the signal's update method instead: this.userInputService.vacationNumberSignal.update(days => days.filter((_, i) => i !== index));
this.userInputService.vacationNumberSignal.update(days => days.filter((_, i) => i !== index));
Directly mutating the array returned from a signal violates signal immutability. Use the signal's `update` method instead: `this.userInputService.vacationNumberSignal.update(days => days.filter((_, i) => i !== index));`
```suggestion:-0+0
this.userInputService.vacationNumberSignal.update(days => days.filter((_, i) => i !== index));
```
Type mismatch: vacationData is VacDay[] from the signal, but optimizeVacations expects an object with {cp: number, rtt: number, other: number} properties. The spread operator { ...vacationData } will create an object with numeric keys, not the expected structure. You need to transform the VacDay[] into the expected format before calling optimizeVacations, or update optimizeVacations to accept the new type.
Type mismatch: `vacationData` is `VacDay[]` from the signal, but `optimizeVacations` expects an object with `{cp: number, rtt: number, other: number}` properties. The spread operator `{ ...vacationData }` will create an object with numeric keys, not the expected structure. You need to transform the `VacDay[]` into the expected format before calling `optimizeVacations`, or update `optimizeVacations` to accept the new type.
This test will fail because the component has a required input day that is not provided. You need to either provide the required input using fixture.componentRef.setInput('day', mockVacDay) before detectChanges(), or add provideAnimationsAsync() to the providers array since the component uses Material components that require animations.
This test will fail because the component has a required input `day` that is not provided. You need to either provide the required input using `fixture.componentRef.setInput('day', mockVacDay)` before `detectChanges()`, or add `provideAnimationsAsync()` to the providers array since the component uses Material components that require animations.
Empty lifecycle hook. Since ngOnInit has no implementation, it should be removed along with the OnInit interface from the class declaration (line 35).
Empty lifecycle hook. Since `ngOnInit` has no implementation, it should be removed along with the `OnInit` interface from the class declaration (line 35).
Unused import. ReactiveFormsModule is no longer used after removing the form-based implementation. This import should be removed from both the import statement and the component's imports array (line 23).
Unused import. `ReactiveFormsModule` is no longer used after removing the form-based implementation. This import should be removed from both the import statement and the component's imports array (line 23).
Unused import and variable. The destroy$ Subject (line 38) is created and completed in ngOnDestroy, but it's never actually used (no .pipe(takeUntil(this.destroy$)) calls remain after form removal). Both the import and the destroy$ property can be removed, along with the OnDestroy interface and ngOnDestroy method.
Unused import and variable. The `destroy$` Subject (line 38) is created and completed in `ngOnDestroy`, but it's never actually used (no `.pipe(takeUntil(this.destroy$))` calls remain after form removal). Both the import and the `destroy$` property can be removed, along with the `OnDestroy` interface and `ngOnDestroy` method.
This PR refactors the vacation management system from a simple count-based approach to a more structured day-tracking system with expiry dates, and upgrades Angular from v20 to v21. Key changes include:
Migration from VacationsNumber (simple object with cp/rtt/other counts) to VacDay[] (array of vacation day objects with IDs, types, counts, and expiry dates)
Addition of async progress tracking for vacation optimization
New DayInput component for managing individual vacation day entries
Angular version upgrade to v21.0.1 with animations support
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 12 comments.
Show a summary per file
File
Description
src/main.server.ts
Adds zone change detection configuration for server-side rendering
src/app/services/user-input-service.ts
Refactors service to use VacDay[] structure instead of VacationsNumber
src/app/services/calendar-service.ts
Updates types, adds async optimization with progress tracking, and implements heuristic limit
src/app/components/userInput/day-input/*
New component for managing individual vacation day inputs
src/app/components/user-input/*
Refactored to use signal-based state with new VacDay structure
src/app/components/calendar-year/*
Updated to handle async optimization and remaining days tracking
package.json
Upgrades Angular from v20 to v21 and adds animations package
These tests reference component.userInputForm which no longer exists after the refactoring. The component now uses daysOff signal instead of a form. These tests should be updated to test the new implementation: verify daysOff is defined, test addDay() and removeFromList() methods.
it('should initialize form with values from service', () => {
expect(component.userInputForm).toBeDefined();
expect(component.userInputForm.get('CP')).toBeDefined();
expect(component.userInputForm.get('RTT')).toBeDefined();
expect(component.userInputForm.get('Others')).toBeDefined();
});
it('should have valid form controls', () => {
const cpControl = component.userInputForm.get('CP');
const rttControl = component.userInputForm.get('RTT');
const othersControl = component.userInputForm.get('Others');
expect(cpControl).toBeTruthy();
expect(rttControl).toBeTruthy();
expect(othersControl).toBeTruthy();
});
**Review:** Commented
## Pull request overview
This PR refactors the vacation management system from a simple count-based approach to a more structured day-tracking system with expiry dates, and upgrades Angular from v20 to v21. Key changes include:
- Migration from `VacationsNumber` (simple object with cp/rtt/other counts) to `VacDay[]` (array of vacation day objects with IDs, types, counts, and expiry dates)
- Addition of async progress tracking for vacation optimization
- New `DayInput` component for managing individual vacation day entries
- Angular version upgrade to v21.0.1 with animations support
### Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 12 comments.
<details>
<summary>Show a summary per file</summary>
| File | Description |
| ---- | ----------- |
| src/main.server.ts | Adds zone change detection configuration for server-side rendering |
| src/app/services/user-input-service.ts | Refactors service to use VacDay[] structure instead of VacationsNumber |
| src/app/services/calendar-service.ts | Updates types, adds async optimization with progress tracking, and implements heuristic limit |
| src/app/components/userInput/day-input/* | New component for managing individual vacation day inputs |
| src/app/components/user-input/* | Refactored to use signal-based state with new VacDay structure |
| src/app/components/calendar-year/* | Updated to handle async optimization and remaining days tracking |
| package.json | Upgrades Angular from v20 to v21 and adds animations package |
</details>
<details>
<summary>Comments suppressed due to low confidence (1)</summary>
**src/app/components/user-input/user-input.spec.ts:40**
* These tests reference `component.userInputForm` which no longer exists after the refactoring. The component now uses `daysOff` signal instead of a form. These tests should be updated to test the new implementation: verify `daysOff` is defined, test `addDay()` and `removeFromList()` methods.
```
it('should initialize form with values from service', () => {
expect(component.userInputForm).toBeDefined();
expect(component.userInputForm.get('CP')).toBeDefined();
expect(component.userInputForm.get('RTT')).toBeDefined();
expect(component.userInputForm.get('Others')).toBeDefined();
});
it('should have valid form controls', () => {
const cpControl = component.userInputForm.get('CP');
const rttControl = component.userInputForm.get('RTT');
const othersControl = component.userInputForm.get('Others');
expect(cpControl).toBeTruthy();
expect(rttControl).toBeTruthy();
expect(othersControl).toBeTruthy();
});
```
</details>
@copilot open a new pull request to apply changes based on the comments in this thread
`@copilot` open a new pull request to apply changes based on the comments in [this thread](https://gitlab.nessar.fr/Nessar/vac-optimizer/-/merge_requests/61#pullrequestreview-3520781340)
Input type should be "text" or "date", not "datetime". The "datetime" type is not a valid HTML input type. For date-only pickers (which this appears to be), use:
Input type should be "text" or "date", not "datetime". The "datetime" type is not a valid HTML input type. For date-only pickers (which this appears to be), use:
```html
<input matInput [matDatepicker]="picker" [value]="day().expiryDate" (dateChange)="addEvent($event)" />
```
```suggestion:-0+0
type="text"
```
Missing dayChange output binding from app-day-input. The DayInput component emits changes through the dayChange output, but this parent component doesn't listen to it. Without handling this event, changes made in the child component won't be properly synchronized. Add the binding:
However, note that with the current direct mutation pattern in the component, you may not need this if you address the signal mutation issues identified in other comments.
Missing `dayChange` output binding from `app-day-input`. The `DayInput` component emits changes through the `dayChange` output, but this parent component doesn't listen to it. Without handling this event, changes made in the child component won't be properly synchronized. Add the binding:
```html
<app-day-input
[day]="day"
(dayChange)="day = $event"
(deleteEntry)="removeFromList($index)">
</app-day-input>
```
However, note that with the current direct mutation pattern in the component, you may not need this if you address the signal mutation issues identified in other comments.
```suggestion:-0+0
<app-day-input [day]="day" (dayChange)="daysOff[$index] = $event" (deleteEntry)="removeFromList($index)"></app-day-input>
```
Direct mutation of input signal object. Input signals return readonly values and should not be mutated directly. Instead, emit a new object with the updated value through dayChange output.
Direct mutation of input signal object. Input signals return readonly values and should not be mutated directly. Instead, emit a new object with the updated value through `dayChange` output.
Consider this approach:
```typescript
onInput(event: Event) {
const value = Number((event.target as HTMLInputElement).value);
this.dayChange.emit({ ...this.day(), numberOfDays: value });
}
```
```suggestion:-0+0
this.dayChange.emit({ ...this.day(), numberOfDays: value });
}
addEvent(event: MatDatepickerInputEvent<Date>) {
this.dayChange.emit({ ...this.day(), expiryDate: event.value! });
```
[nitpick] Commented-out code should be removed. If the mat-list-item wrapper was removed intentionally, the closing comment should be deleted as well.
[nitpick] Commented-out code should be removed. If the `mat-list-item` wrapper was removed intentionally, the closing comment should be deleted as well.
```suggestion:-0+0
```
Empty mat-option without value serves no clear purpose. If this is meant to be a "clear selection" option, it should have a descriptive label. If not needed, remove it:
Empty `mat-option` without value serves no clear purpose. If this is meant to be a "clear selection" option, it should have a descriptive label. If not needed, remove it:
```html
<mat-select (valueChange)="..." [value]="day().type">
@for (dayType of selectableDayTypes; track $index) {
<mat-option [value]="dayType">{{ tooltipTypeMapping[dayType] }}</mat-option>
}
</mat-select>
```
```suggestion:-0+0
```
Direct mutation of signal array. The code mutates this.daysOff which is the result of calling a signal (this.userInputService.vacationNumberSignal()), but this doesn't trigger signal updates. The mutation affects the underlying array but Angular's change detection won't be notified.
This issue is partially acknowledged in triggerComputation() (line 80), but the pattern of mutating and then manually triggering updates is error-prone. Consider using update() instead:
Direct mutation of signal array. The code mutates `this.daysOff` which is the result of calling a signal (`this.userInputService.vacationNumberSignal()`), but this doesn't trigger signal updates. The mutation affects the underlying array but Angular's change detection won't be notified.
This issue is partially acknowledged in `triggerComputation()` (line 80), but the pattern of mutating and then manually triggering updates is error-prone. Consider using `update()` instead:
```typescript
public addDay() {
this.userInputService.vacationNumberSignal.update(days => [...days, this.userInputService.createVacationDay()]);
}
```
Empty ngOnInit() method serves no purpose and should be removed. If the OnInit lifecycle hook isn't needed, also remove OnInit from the implements clause and the import.
Empty `ngOnInit()` method serves no purpose and should be removed. If the `OnInit` lifecycle hook isn't needed, also remove `OnInit` from the implements clause and the import.
Type casting day.type as unknown as DayType indicates a design issue. Since SelectableDayType is a subset of DayType (with values CP, RTT, OTHER), the type system should handle this naturally. Consider redesigning the types to avoid unsafe casting, or if the relationship is valid, use a direct cast: day.type as DayType.
const typeName = tooltipTypeMapping[day.type as DayType];
Type casting `day.type as unknown as DayType` indicates a design issue. Since `SelectableDayType` is a subset of `DayType` (with values CP, RTT, OTHER), the type system should handle this naturally. Consider redesigning the types to avoid unsafe casting, or if the relationship is valid, use a direct cast: `day.type as DayType`.
```suggestion:-0+0
const typeName = tooltipTypeMapping[day.type as DayType];
```
The destroy$ Subject is defined but never used since ngOnInit() no longer subscribes to observables. If not needed for cleanup, remove both destroy$ and the OnDestroy implementation.
The `destroy$` Subject is defined but never used since `ngOnInit()` no longer subscribes to observables. If not needed for cleanup, remove both `destroy$` and the `OnDestroy` implementation.
Select dropdown for day type is missing an accessible label. Screen readers won't be able to describe this field to users. Add a <mat-label>:
<mat-form-fieldclass="dayTypeInput"><mat-label>Type of leave</mat-label><mat-select...>
Select dropdown for day type is missing an accessible label. Screen readers won't be able to describe this field to users. Add a `<mat-label>`:
```html
<mat-form-field class="dayTypeInput">
<mat-label>Type of leave</mat-label>
<mat-select ...>
```
::ng-deep is deprecated and should be avoided. It pierces component encapsulation and can cause unexpected styling issues. Consider using Angular's host context or moving this style to a global stylesheet if it needs to affect Material components. Alternatively, use ViewEncapsulation.None if you need to style child components, though this is also not recommended.
`::ng-deep` is deprecated and should be avoided. It pierces component encapsulation and can cause unexpected styling issues. Consider using Angular's host context or moving this style to a global stylesheet if it needs to affect Material components. Alternatively, use ViewEncapsulation.None if you need to style child components, though this is also not recommended.
Unused import in component's imports array. ReactiveFormsModule should be removed from the imports array since reactive forms are no longer used in this component.
Unused import in component's imports array. `ReactiveFormsModule` should be removed from the imports array since reactive forms are no longer used in this component.
```suggestion:-0+0
```
Direct mutation of input signal object and non-null assertion without validation. Input signals should not be mutated directly, and event.value may be null for invalid dates.
if (event.value) {
this.dayChange.emit({ ...this.day(), expiryDate: event.value });
}
Direct mutation of input signal object and non-null assertion without validation. Input signals should not be mutated directly, and `event.value` may be null for invalid dates.
Consider this approach:
```typescript
addEvent(event: MatDatepickerInputEvent<Date>) {
if (event.value) {
this.dayChange.emit({ ...this.day(), expiryDate: event.value });
}
}
```
```suggestion:-0+0
if (event.value) {
this.dayChange.emit({ ...this.day(), expiryDate: event.value });
}
```
this.userInputService.vacationNumberSignal.update(days => days.filter((_, i) => i !== index));
Direct mutation of signal array. Similar to `addDay()`, this mutates the signal's underlying array without triggering updates. Use `update()` instead:
```typescript
public removeFromList(index: number) {
this.userInputService.vacationNumberSignal.update(days => days.filter((_, i) => i !== index));
}
```
```suggestion:-0+0
this.userInputService.vacationNumberSignal.update(days => days.filter((_, i) => i !== index));
```
The test will fail because `DayInput` has a required input `day` that is not provided. The component requires a `VacDay` object to be passed as input.
Add the required input:
```typescript
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DayInput],
}).compileComponents();
fixture = TestBed.createComponent(DayInput);
fixture.componentRef.setInput('day', {
id: 'test-id',
type: SelectableDayType.CP,
numberOfDays: 5,
expiryDate: new Date()
});
component = fixture.componentInstance;
fixture.detectChanges();
});
```
Missing test provider for animations. The DayInput component uses Material components that depend on animations. Add provideAnimationsAsync() to the test configuration to avoid console warnings and potential test failures:
Missing test provider for animations. The `DayInput` component uses Material components that depend on animations. Add `provideAnimationsAsync()` to the test configuration to avoid console warnings and potential test failures:
```typescript
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
await TestBed.configureTestingModule({
imports: [DayInput],
providers: [provideAnimationsAsync()],
}).compileComponents();
```
Shallow copy doesn't deep copy the expiryDate Date objects. Since optimizeVacations() modifies numberOfDays on the vacation days objects, and those objects contain mutable Date objects, a shallow spread may not be sufficient if dates are mutated. While the current code appears to only mutate numberOfDays, the comment claims "deep copy" but provides shallow copy.
Consider using a proper deep copy or clarify the comment:
Shallow copy doesn't deep copy the `expiryDate` Date objects. Since `optimizeVacations()` modifies `numberOfDays` on the vacation days objects, and those objects contain mutable `Date` objects, a shallow spread may not be sufficient if dates are mutated. While the current code appears to only mutate `numberOfDays`, the comment claims "deep copy" but provides shallow copy.
Consider using a proper deep copy or clarify the comment:
```typescript
const vacationDaysCopy = vacationData.map((vd) => ({
...vd,
expiryDate: new Date(vd.expiryDate)
}));
```
```suggestion:-0+0
const vacationDaysCopy = vacationData.map((vd) => ({
...vd,
expiryDate: vd.expiryDate ? new Date(vd.expiryDate) : vd.expiryDate,
}));
```
Missing tests for the component's key behaviors. The test suite only verifies component creation but doesn't test the onInput() and addEvent() methods, or the output events (dayChange, deleteEntry). Given the repository has comprehensive test coverage for other components and services, consider adding tests for:
Input changes emitting dayChange event
Date changes emitting dayChange event
Delete button emitting deleteEntry event
Missing tests for the component's key behaviors. The test suite only verifies component creation but doesn't test the `onInput()` and `addEvent()` methods, or the output events (`dayChange`, `deleteEntry`). Given the repository has comprehensive test coverage for other components and services, consider adding tests for:
- Input changes emitting `dayChange` event
- Date changes emitting `dayChange` event
- Delete button emitting `deleteEntry` event
The valueChange event emits only the selected value, not an object with a value property. Accessing $event.value will result in undefined. Also, emitting just the type value instead of updating the entire VacDay object doesn't align with how other changes are handled.
The `valueChange` event emits only the selected value, not an object with a `value` property. Accessing `$event.value` will result in `undefined`. Also, emitting just the type value instead of updating the entire `VacDay` object doesn't align with how other changes are handled.
Use:
```html
<mat-select (valueChange)="day().type = $event; dayChange.emit(day())" [value]="day().type">
```
Or better, follow the pattern of not mutating inputs:
```html
<mat-select (valueChange)="dayChange.emit({ ...day(), type: $event })" [value]="day().type">
```
```suggestion:-0+0
<mat-select (valueChange)="dayChange.emit({ ...day(), type: $event })" [value]="day().type">
```
No validation for negative or invalid number inputs. The HTML has min="0", but the Number() conversion can still produce negative values or NaN if the user enters invalid text. Consider adding validation:
const value = Number((event.target as HTMLInputElement).value);
if (isNaN(value) || value < 0) return;
No validation for negative or invalid number inputs. The HTML has `min="0"`, but the `Number()` conversion can still produce negative values or `NaN` if the user enters invalid text. Consider adding validation:
```typescript
onInput(event: Event) {
const value = Number((event.target as HTMLInputElement).value);
if (isNaN(value) || value < 0) return;
this.dayChange.emit({ ...this.day(), numberOfDays: value });
}
```
```suggestion:-0+0
const value = Number((event.target as HTMLInputElement).value);
if (isNaN(value) || value < 0) return;
```
Type casting vacDay.type as unknown as DayType indicates a design issue. Since SelectableDayType enum values are defined as equal to DayType values (lines 207-211), a direct cast should work: vacDay.type as DayType. The double cast through unknown suggests the types aren't properly aligned.
const vacType = vacDay.type as DayType;
Type casting `vacDay.type as unknown as DayType` indicates a design issue. Since `SelectableDayType` enum values are defined as equal to `DayType` values (lines 207-211), a direct cast should work: `vacDay.type as DayType`. The double cast through `unknown` suggests the types aren't properly aligned.
```suggestion:-0+0
const vacType = vacDay.type as DayType;
```
The daysOff getter directly returns a signal instead of its value. This is inconsistent with Angular best practices for signals. Consider either making this a computed signal or calling the signal to return its value, e.g., return this.userInputService.vacationNumberSignal();
The `daysOff` getter directly returns a signal instead of its value. This is inconsistent with Angular best practices for signals. Consider either making this a computed signal or calling the signal to return its value, e.g., `return this.userInputService.vacationNumberSignal();`
The clickable card uses (click)="addDay()" on a mat-card element instead of the button inside it. This creates a redundant click handler since the button inside already has keyboard accessibility. The card itself isn't keyboard-accessible unless additional attributes like tabindex="0" and (keydown.enter) or (keydown.space) are added. Consider moving the click handler to just the button, or add proper keyboard event handlers to the card.
The clickable card uses `(click)="addDay()"` on a `mat-card` element instead of the button inside it. This creates a redundant click handler since the button inside already has keyboard accessibility. The card itself isn't keyboard-accessible unless additional attributes like `tabindex="0"` and `(keydown.enter)` or `(keydown.space)` are added. Consider moving the click handler to just the button, or add proper keyboard event handlers to the card.
The number input field lacks a label, making it inaccessible to screen reader users. Consider adding a mat-label inside the mat-form-field to describe what this input represents (e.g., "Number of days").
The number input field lacks a label, making it inaccessible to screen reader users. Consider adding a `mat-label` inside the `mat-form-field` to describe what this input represents (e.g., "Number of days").
The non-null assertion operator ! is used on event.value, but there's no guarantee that event.value is non-null. If the user clears the date field, event.value could be null, causing a runtime error. Consider handling the null case explicitly, e.g., by not emitting if the value is null, or using a default date.
The non-null assertion operator `!` is used on `event.value`, but there's no guarantee that `event.value` is non-null. If the user clears the date field, `event.value` could be `null`, causing a runtime error. Consider handling the null case explicitly, e.g., by not emitting if the value is null, or using a default date.
```suggestion:-0+0
if (event.value !== null) {
this.dayChange.emit({ ...this.day(), expiryDate: event.value });
}
```
[nitpick] The explicit type annotation : UserInputService is redundant when using inject(), as TypeScript can infer the type. Consider simplifying to: private userInputService = inject(UserInputService);
[nitpick] The explicit type annotation `: UserInputService` is redundant when using `inject()`, as TypeScript can infer the type. Consider simplifying to: `private userInputService = inject(UserInputService);`
```suggestion:-0+0
private userInputService = inject(UserInputService);
```
This line has the same date calculation issue. The duplicated logic appears 4 times in this file (lines 19, 25, 31, and 41). Extract this into a helper method like private getExpiryDateOneYearFromNow(): Date to eliminate duplication.
This line has the same date calculation issue. The duplicated logic appears 4 times in this file (lines 19, 25, 31, and 41). Extract this into a helper method like `private getExpiryDateOneYearFromNow(): Date` to eliminate duplication.
Commented-out code should be removed rather than left in the codebase. If this mat-list-item wrapper is no longer needed, delete it. If it might be needed in the future, rely on version control to retrieve it.
Commented-out code should be removed rather than left in the codebase. If this `mat-list-item` wrapper is no longer needed, delete it. If it might be needed in the future, rely on version control to retrieve it.
The type cast day.type as unknown as DayType is a code smell. Since SelectableDayType values are derived from DayType, this cast should be unnecessary. Consider refactoring the type relationship or using a type-safe mapping function.
The type cast `day.type as unknown as DayType` is a code smell. Since `SelectableDayType` values are derived from `DayType`, this cast should be unnecessary. Consider refactoring the type relationship or using a type-safe mapping function.
```suggestion:-0+0
const typeName = isDayType(day.type)
? tooltipTypeMapping[day.type]
: 'Unknown';
```
The component has been significantly refactored with new methods (addDay, removeFromList, updateDay, triggerComputation) and computed signals, but the test file only adds provideAnimationsAsync(). Consider adding tests to verify the new functionality works correctly.
The component has been significantly refactored with new methods (`addDay`, `removeFromList`, `updateDay`, `triggerComputation`) and computed signals, but the test file only adds `provideAnimationsAsync()`. Consider adding tests to verify the new functionality works correctly.
Using ::ng-deep is deprecated and discouraged in Angular. It's a workaround that pierces view encapsulation and can cause global style pollution. Consider using Angular Material's theming system or custom form field appearance options instead to hide the subscript wrapper.
/* To hide the subscript wrapper, add a custom class to your mat-form-field in the template, e.g. <mat-form-field class="no-subscript"> */
/* Then use the following selector: */
.no-subscript .mat-mdc-form-field-subscript-wrapper {
Using `::ng-deep` is deprecated and discouraged in Angular. It's a workaround that pierces view encapsulation and can cause global style pollution. Consider using Angular Material's theming system or custom form field appearance options instead to hide the subscript wrapper.
```suggestion:-0+0
/* To hide the subscript wrapper, add a custom class to your mat-form-field in the template, e.g. <mat-form-field class="no-subscript"> */
/* Then use the following selector: */
.no-subscript .mat-mdc-form-field-subscript-wrapper {
```
The date calculation creates multiple new Date() objects unnecessarily. The expression new Date(new Date().setFullYear(...)) first creates a Date, mutates it with setFullYear (which returns a timestamp), then creates a new Date from that timestamp. This is convoluted. Use a clearer approach: const date = new Date(); date.setFullYear(date.getFullYear() + 1); return date; or simply new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)
The date calculation creates multiple `new Date()` objects unnecessarily. The expression `new Date(new Date().setFullYear(...))` first creates a Date, mutates it with `setFullYear` (which returns a timestamp), then creates a new Date from that timestamp. This is convoluted. Use a clearer approach: `const date = new Date(); date.setFullYear(date.getFullYear() + 1); return date;` or simply `new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)`
The test suite only includes a basic creation test. The component has several behavioral methods (onInput, onTypeChange, addEvent) that lack test coverage. Consider adding tests to verify:
onInput emits dayChange with updated numberOfDays
onTypeChange emits dayChange with updated type
addEvent emits dayChange with updated expiryDate
deleteEntry output works correctly
The test suite only includes a basic creation test. The component has several behavioral methods (`onInput`, `onTypeChange`, `addEvent`) that lack test coverage. Consider adding tests to verify:
- `onInput` emits `dayChange` with updated `numberOfDays`
- `onTypeChange` emits `dayChange` with updated `type`
- `addEvent` emits `dayChange` with updated `expiryDate`
- `deleteEntry` output works correctly
On each iteration of the while loop, the code creates a copy of vacationDays, filters it, and sorts it. This is inefficient for large arrays. Consider moving the filtering and sorting outside the loop, or using a more efficient data structure like a priority queue to avoid repeated sorting.
On each iteration of the while loop, the code creates a copy of `vacationDays`, filters it, and sorts it. This is inefficient for large arrays. Consider moving the filtering and sorting outside the loop, or using a more efficient data structure like a priority queue to avoid repeated sorting.
This line has the same date calculation issue as line 19. Consider extracting this logic into a helper method to avoid duplication and improve maintainability.
This line has the same date calculation issue as line 19. Consider extracting this logic into a helper method to avoid duplication and improve maintainability.
Adding --legacy-peer-deps flag suggests there are peer dependency conflicts with Angular 21. While this may work, it bypasses npm's peer dependency validation which could hide compatibility issues. Consider documenting why this flag is necessary, or investigate resolving the underlying peer dependency conflicts.
Adding `--legacy-peer-deps` flag suggests there are peer dependency conflicts with Angular 21. While this may work, it bypasses npm's peer dependency validation which could hide compatibility issues. Consider documenting why this flag is necessary, or investigate resolving the underlying peer dependency conflicts.
Copilot reviewed 14 out of 15 changed files in this pull request and generated 19 comments.
Comments suppressed due to low confidence (1)
src/app/services/calendar-service.ts:925
The lookForVacation method has been modified to filter candidate days based on the expiry date of the VacDay object. This new behavior lacks test coverage to ensure days after the expiry date are correctly excluded from consideration.
lookForVacation(
vacDay: VacDay,
calendarService: CalendarService,
): { apply: () => void; heuristic: number } {
const daysOfYear = calendarService.monthsInAYearFromNow();
const now = new Date();
now.setHours(0, 0, 0, 0);
const nowTime = now.getTime();
const expiryTime = vacDay.expiryDate.getTime();
// Preprocess: filter out past days, already selected days, and days after expiry date
const candidateDays: Date[] = [];
for (const month of daysOfYear) {
for (const week of month) {
for (const day of week) {
if (day) {
day.setHours(0, 0, 0, 0);
const dayTime = day.getTime();
if (dayTime >= nowTime && dayTime <= expiryTime && !this.isSelected(day)) {
candidateDays.push(day);
}
}
}
}
}
// Early exit if no candidates
if (candidateDays.length === 0) {
return { apply: () => {}, heuristic: SelectedDates.NO_VALID_STRATEGY_HEURISTIC };
}
const vacType = vacDay.type as unknown as DayType;
const tempDatesBackup = [...this._datesSelected];
let bestStrategy: { apply: () => void; heuristic: number; value: Date } | null = null;
let bestHeuristic = -Infinity;
let daysWithBestHeuristic = 0;
// Evaluate each candidate day
for (const day of candidateDays) {
// Reset to backup state
this._datesSelected = [...tempDatesBackup];
this.datesSelected = this._datesSelected.slice();
this.grouping();
// Mark this day as vacation (reuse the day object since it's already normalized)
this.datesSelected.push(new SelectedDate(vacType, new DateRange<Date>(day, day)));
const heuristic = this.computeHeuristics();
// Track only if better than current best
if (heuristic > bestHeuristic) {
bestHeuristic = heuristic;
daysWithBestHeuristic = 1;
const capturedDay = day;
bestStrategy = {
apply: () => {
this._datesSelected = [...tempDatesBackup];
this.push(
new SelectedDate(
vacType,
new DateRange<Date>(capturedDay, capturedDay),
),
);
},
heuristic: heuristic,
value: capturedDay,
};
} else if (heuristic === bestHeuristic) {
daysWithBestHeuristic++;
}
}
// Check if too many days have the same best heuristic after evaluating all days
if (daysWithBestHeuristic > SelectedDates.MAX_DAYS_WITH_SAME_HEURISTIC) {
// Restore backup before stopping
this._datesSelected = [...tempDatesBackup];
this.datesSelected = this._datesSelected.slice();
this.grouping();
// Stop optimization - let user decide manually
return {
apply: () => {},
heuristic: SelectedDates.NO_VALID_STRATEGY_HEURISTIC,
};
}
// Restore backup before returning
this._datesSelected = [...tempDatesBackup];
this.datesSelected = this._datesSelected.slice();
this.grouping();
// Apply best strategy if found
if (bestStrategy) {
bestStrategy.apply();
return bestStrategy;
}
return { apply: () => {}, heuristic: SelectedDates.NO_VALID_STRATEGY_HEURISTIC };
}
**Review:** Commented
## Pull request overview
Copilot reviewed 14 out of 15 changed files in this pull request and generated 19 comments.
<details>
<summary>Comments suppressed due to low confidence (1)</summary>
**src/app/services/calendar-service.ts:925**
* The `lookForVacation` method has been modified to filter candidate days based on the expiry date of the `VacDay` object. This new behavior lacks test coverage to ensure days after the expiry date are correctly excluded from consideration.
```
lookForVacation(
vacDay: VacDay,
calendarService: CalendarService,
): { apply: () => void; heuristic: number } {
const daysOfYear = calendarService.monthsInAYearFromNow();
const now = new Date();
now.setHours(0, 0, 0, 0);
const nowTime = now.getTime();
const expiryTime = vacDay.expiryDate.getTime();
// Preprocess: filter out past days, already selected days, and days after expiry date
const candidateDays: Date[] = [];
for (const month of daysOfYear) {
for (const week of month) {
for (const day of week) {
if (day) {
day.setHours(0, 0, 0, 0);
const dayTime = day.getTime();
if (dayTime >= nowTime && dayTime <= expiryTime && !this.isSelected(day)) {
candidateDays.push(day);
}
}
}
}
}
// Early exit if no candidates
if (candidateDays.length === 0) {
return { apply: () => {}, heuristic: SelectedDates.NO_VALID_STRATEGY_HEURISTIC };
}
const vacType = vacDay.type as unknown as DayType;
const tempDatesBackup = [...this._datesSelected];
let bestStrategy: { apply: () => void; heuristic: number; value: Date } | null = null;
let bestHeuristic = -Infinity;
let daysWithBestHeuristic = 0;
// Evaluate each candidate day
for (const day of candidateDays) {
// Reset to backup state
this._datesSelected = [...tempDatesBackup];
this.datesSelected = this._datesSelected.slice();
this.grouping();
// Mark this day as vacation (reuse the day object since it's already normalized)
this.datesSelected.push(new SelectedDate(vacType, new DateRange<Date>(day, day)));
const heuristic = this.computeHeuristics();
// Track only if better than current best
if (heuristic > bestHeuristic) {
bestHeuristic = heuristic;
daysWithBestHeuristic = 1;
const capturedDay = day;
bestStrategy = {
apply: () => {
this._datesSelected = [...tempDatesBackup];
this.push(
new SelectedDate(
vacType,
new DateRange<Date>(capturedDay, capturedDay),
),
);
},
heuristic: heuristic,
value: capturedDay,
};
} else if (heuristic === bestHeuristic) {
daysWithBestHeuristic++;
}
}
// Check if too many days have the same best heuristic after evaluating all days
if (daysWithBestHeuristic > SelectedDates.MAX_DAYS_WITH_SAME_HEURISTIC) {
// Restore backup before stopping
this._datesSelected = [...tempDatesBackup];
this.datesSelected = this._datesSelected.slice();
this.grouping();
// Stop optimization - let user decide manually
return {
apply: () => {},
heuristic: SelectedDates.NO_VALID_STRATEGY_HEURISTIC,
};
}
// Restore backup before returning
this._datesSelected = [...tempDatesBackup];
this.datesSelected = this._datesSelected.slice();
this.grouping();
// Apply best strategy if found
if (bestStrategy) {
bestStrategy.apply();
return bestStrategy;
}
return { apply: () => {}, heuristic: SelectedDates.NO_VALID_STRATEGY_HEURISTIC };
}
```
</details>
Source requested reviewers: admin-backup (GitLab user 3).
The linked history record retains original IDs and source dates. No historical identity was merged into nessar or granted new authentication permissions.
<!-- gitlab-to-gitea-assignment:35:MergeRequest:61 -->
Preserved source assignment/reviewer metadata. Historical accounts stay inactive.
Source requested reviewers: admin-backup (GitLab user 3).
The linked history record retains original IDs and source dates. No historical identity was merged into nessar or granted new authentication permissions.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
requested review from
@CopilotThe mat-select event handler is incorrect. The
valueChangeevent emits the new value directly, not an event object. Change$event.valueto just$event, or use the proper approach by updating the day object and emitting it. The current code will emitundefined.Direct mutation of an input signal's value is not allowed and violates Angular's signal immutability principle. Instead, create a new object with the updated property. Replace lines 93-94 with:
this.dayChange.emit({ ...this.day(), numberOfDays: value });Direct mutation of an input signal's value is not allowed and violates Angular's signal immutability principle. Instead, create a new object with the updated property. Replace lines 98-99 with:
this.dayChange.emit({ ...this.day(), expiryDate: event.value! });Directly mutating the array returned from a signal violates signal immutability. Use the signal's
updatemethod instead:this.userInputService.vacationNumberSignal.update(days => [...days, this.userInputService.createVacationDay()]);Using
anytype defeats TypeScript's type safety. The parameter type should match the actual data structure being used. Based on the code, this should beVacationsNumber(with properties cp, rtt, other), not the newVacDay[]type.Using
anytype defeats TypeScript's type safety. The parameter type should beVacationsNumber(with properties cp, rtt, other) to match how it's used throughout the method.Directly mutating the array returned from a signal violates signal immutability. Use the signal's
updatemethod instead:this.userInputService.vacationNumberSignal.update(days => days.filter((_, i) => i !== index));Type mismatch:
vacationDataisVacDay[]from the signal, butoptimizeVacationsexpects an object with{cp: number, rtt: number, other: number}properties. The spread operator{ ...vacationData }will create an object with numeric keys, not the expected structure. You need to transform theVacDay[]into the expected format before callingoptimizeVacations, or updateoptimizeVacationsto accept the new type.This test will fail because the component has a required input
daythat is not provided. You need to either provide the required input usingfixture.componentRef.setInput('day', mockVacDay)beforedetectChanges(), or addprovideAnimationsAsync()to the providers array since the component uses Material components that require animations.Empty lifecycle hook. Since
ngOnInithas no implementation, it should be removed along with theOnInitinterface from the class declaration (line 35).Unused import.
ReactiveFormsModuleis no longer used after removing the form-based implementation. This import should be removed from both the import statement and the component's imports array (line 23).Unused import and variable. The
destroy$Subject (line 38) is created and completed inngOnDestroy, but it's never actually used (no.pipe(takeUntil(this.destroy$))calls remain after form removal). Both the import and thedestroy$property can be removed, along with theOnDestroyinterface andngOnDestroymethod.Review: Commented
Pull request overview
This PR refactors the vacation management system from a simple count-based approach to a more structured day-tracking system with expiry dates, and upgrades Angular from v20 to v21. Key changes include:
VacationsNumber(simple object with cp/rtt/other counts) toVacDay[](array of vacation day objects with IDs, types, counts, and expiry dates)DayInputcomponent for managing individual vacation day entriesReviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 12 comments.
Show a summary per file
Comments suppressed due to low confidence (1)
src/app/components/user-input/user-input.spec.ts:40
component.userInputFormwhich no longer exists after the refactoring. The component now usesdaysOffsignal instead of a form. These tests should be updated to test the new implementation: verifydaysOffis defined, testaddDay()andremoveFromList()methods.requested review from
@Copilot@copilotopen a new pull request to apply changes based on the comments in this threadmentioned in merge request !62
@THoguetI've opened a new pull request, #62, to work on those changes. Once the pull request is ready, I'll request review from you.Input type should be "text" or "date", not "datetime". The "datetime" type is not a valid HTML input type. For date-only pickers (which this appears to be), use:
Missing
dayChangeoutput binding fromapp-day-input. TheDayInputcomponent emits changes through thedayChangeoutput, but this parent component doesn't listen to it. Without handling this event, changes made in the child component won't be properly synchronized. Add the binding:However, note that with the current direct mutation pattern in the component, you may not need this if you address the signal mutation issues identified in other comments.
Direct mutation of input signal object. Input signals return readonly values and should not be mutated directly. Instead, emit a new object with the updated value through
dayChangeoutput.Consider this approach:
[nitpick] Commented-out code should be removed. If the
mat-list-itemwrapper was removed intentionally, the closing comment should be deleted as well.Empty
mat-optionwithout value serves no clear purpose. If this is meant to be a "clear selection" option, it should have a descriptive label. If not needed, remove it:Direct mutation of signal array. The code mutates
this.daysOffwhich is the result of calling a signal (this.userInputService.vacationNumberSignal()), but this doesn't trigger signal updates. The mutation affects the underlying array but Angular's change detection won't be notified.This issue is partially acknowledged in
triggerComputation()(line 80), but the pattern of mutating and then manually triggering updates is error-prone. Consider usingupdate()instead:Empty
ngOnInit()method serves no purpose and should be removed. If theOnInitlifecycle hook isn't needed, also removeOnInitfrom the implements clause and the import.[nitpick] Commented-out code should be removed. If the
mat-list-itemwrapper was removed intentionally, the comment should be deleted as well.Type casting
day.type as unknown as DayTypeindicates a design issue. SinceSelectableDayTypeis a subset ofDayType(with values CP, RTT, OTHER), the type system should handle this naturally. Consider redesigning the types to avoid unsafe casting, or if the relationship is valid, use a direct cast:day.type as DayType.The
destroy$Subject is defined but never used sincengOnInit()no longer subscribes to observables. If not needed for cleanup, remove bothdestroy$and theOnDestroyimplementation.Select dropdown for day type is missing an accessible label. Screen readers won't be able to describe this field to users. Add a
<mat-label>:::ng-deepis deprecated and should be avoided. It pierces component encapsulation and can cause unexpected styling issues. Consider using Angular's host context or moving this style to a global stylesheet if it needs to affect Material components. Alternatively, use ViewEncapsulation.None if you need to style child components, though this is also not recommended.Unused import in component's imports array.
ReactiveFormsModuleshould be removed from the imports array since reactive forms are no longer used in this component.Direct mutation of input signal object and non-null assertion without validation. Input signals should not be mutated directly, and
event.valuemay be null for invalid dates.Consider this approach:
Direct mutation of signal array. Similar to
addDay(), this mutates the signal's underlying array without triggering updates. Useupdate()instead:The test will fail because
DayInputhas a required inputdaythat is not provided. The component requires aVacDayobject to be passed as input.Add the required input:
Missing test provider for animations. The
DayInputcomponent uses Material components that depend on animations. AddprovideAnimationsAsync()to the test configuration to avoid console warnings and potential test failures:Shallow copy doesn't deep copy the
expiryDateDate objects. SinceoptimizeVacations()modifiesnumberOfDayson the vacation days objects, and those objects contain mutableDateobjects, a shallow spread may not be sufficient if dates are mutated. While the current code appears to only mutatenumberOfDays, the comment claims "deep copy" but provides shallow copy.Consider using a proper deep copy or clarify the comment:
Missing tests for the component's key behaviors. The test suite only verifies component creation but doesn't test the
onInput()andaddEvent()methods, or the output events (dayChange,deleteEntry). Given the repository has comprehensive test coverage for other components and services, consider adding tests for:dayChangeeventdayChangeeventdeleteEntryeventThe
valueChangeevent emits only the selected value, not an object with avalueproperty. Accessing$event.valuewill result inundefined. Also, emitting just the type value instead of updating the entireVacDayobject doesn't align with how other changes are handled.Use:
Or better, follow the pattern of not mutating inputs:
No validation for negative or invalid number inputs. The HTML has
min="0", but theNumber()conversion can still produce negative values orNaNif the user enters invalid text. Consider adding validation:Type casting
vacDay.type as unknown as DayTypeindicates a design issue. SinceSelectableDayTypeenum values are defined as equal toDayTypevalues (lines 207-211), a direct cast should work:vacDay.type as DayType. The double cast throughunknownsuggests the types aren't properly aligned.Review: Commented
Pull request overview
Copilot reviewed 13 out of 14 changed files in this pull request and generated 22 comments.
@copilotfix pipelinementioned in merge request !63
@THoguetI've opened a new pull request, #63, to work on those changes. Once the pull request is ready, I'll request review from you.requested review from
@Copilot@copilotfix unit test and lintermentioned in merge request !64
@THoguetI've opened a new pull request, #64, to work on those changes. Once the pull request is ready, I'll request review from you.The
daysOffgetter directly returns a signal instead of its value. This is inconsistent with Angular best practices for signals. Consider either making this a computed signal or calling the signal to return its value, e.g.,return this.userInputService.vacationNumberSignal();The clickable card uses
(click)="addDay()"on amat-cardelement instead of the button inside it. This creates a redundant click handler since the button inside already has keyboard accessibility. The card itself isn't keyboard-accessible unless additional attributes liketabindex="0"and(keydown.enter)or(keydown.space)are added. Consider moving the click handler to just the button, or add proper keyboard event handlers to the card.The number input field lacks a label, making it inaccessible to screen reader users. Consider adding a
mat-labelinside themat-form-fieldto describe what this input represents (e.g., "Number of days").The non-null assertion operator
!is used onevent.value, but there's no guarantee thatevent.valueis non-null. If the user clears the date field,event.valuecould benull, causing a runtime error. Consider handling the null case explicitly, e.g., by not emitting if the value is null, or using a default date.[nitpick] The explicit type annotation
: UserInputServiceis redundant when usinginject(), as TypeScript can infer the type. Consider simplifying to:private userInputService = inject(UserInputService);This line has the same date calculation issue. The duplicated logic appears 4 times in this file (lines 19, 25, 31, and 41). Extract this into a helper method like
private getExpiryDateOneYearFromNow(): Dateto eliminate duplication.Commented-out code should be removed rather than left in the codebase. If this
mat-list-itemwrapper is no longer needed, delete it. If it might be needed in the future, rely on version control to retrieve it.Commented-out code should be removed. See comment on line 1.
The type cast
day.type as unknown as DayTypeis a code smell. SinceSelectableDayTypevalues are derived fromDayType, this cast should be unnecessary. Consider refactoring the type relationship or using a type-safe mapping function.The component has been significantly refactored with new methods (
addDay,removeFromList,updateDay,triggerComputation) and computed signals, but the test file only addsprovideAnimationsAsync(). Consider adding tests to verify the new functionality works correctly.Using
::ng-deepis deprecated and discouraged in Angular. It's a workaround that pierces view encapsulation and can cause global style pollution. Consider using Angular Material's theming system or custom form field appearance options instead to hide the subscript wrapper.The date calculation creates multiple
new Date()objects unnecessarily. The expressionnew Date(new Date().setFullYear(...))first creates a Date, mutates it withsetFullYear(which returns a timestamp), then creates a new Date from that timestamp. This is convoluted. Use a clearer approach:const date = new Date(); date.setFullYear(date.getFullYear() + 1); return date;or simplynew Date(Date.now() + 365 * 24 * 60 * 60 * 1000)This line has the same date calculation issue as the previous occurrences. See comments on lines 19, 25, and 31.
The test suite only includes a basic creation test. The component has several behavioral methods (
onInput,onTypeChange,addEvent) that lack test coverage. Consider adding tests to verify:onInputemitsdayChangewith updatednumberOfDaysonTypeChangeemitsdayChangewith updatedtypeaddEventemitsdayChangewith updatedexpiryDatedeleteEntryoutput works correctlyOn each iteration of the while loop, the code creates a copy of
vacationDays, filters it, and sorts it. This is inefficient for large arrays. Consider moving the filtering and sorting outside the loop, or using a more efficient data structure like a priority queue to avoid repeated sorting.This line has the same date calculation issue as line 19. Consider extracting this logic into a helper method to avoid duplication and improve maintainability.
Adding
--legacy-peer-depsflag suggests there are peer dependency conflicts with Angular 21. While this may work, it bypasses npm's peer dependency validation which could hide compatibility issues. Consider documenting why this flag is necessary, or investigate resolving the underlying peer dependency conflicts.Review: Commented
Pull request overview
Copilot reviewed 14 out of 15 changed files in this pull request and generated 19 comments.
Comments suppressed due to low confidence (1)
src/app/services/calendar-service.ts:925
lookForVacationmethod has been modified to filter candidate days based on the expiry date of theVacDayobject. This new behavior lacks test coverage to ensure days after the expiry date are correctly excluded from consideration.Merged by: THoguet at 2025-11-30 02:17:57 UTC
Preserved source assignment/reviewer metadata. Historical accounts stay inactive.
Source requested reviewers: admin-backup (GitLab user 3).
The linked history record retains original IDs and source dates. No historical identity was merged into nessar or granted new authentication permissions.
Preserved GitLab !61 history: source attribution, exact timestamps, discussion grouping and review positions. Historical diff snapshots: 1; visible source notes: 67.
gitlab-35-mergerequest-61.md (SHA-256
0ff43ee494c7c3e8fcc0b8dc244691ef9a8601103ead1aef0fa7b0098e04929d)gitlab-35-mergerequest-61.json (SHA-256
8863c25530d8af4f0e797b36868d94d2eb73be01dd7e6c4e50437ed99ae346e4)Old CI job execution and reports are omitted. Original source files and rollback backups remain protected.