{ "CI_job_history_included": false, "approvals": [], "diff_versions": [ { "commits": [ { "authored_date": null, "commit_author_id": null, "committed_date": null, "committer_id": null, "merge_request_commits_metadata_id": 204, "merge_request_diff_id": 107, "message": null, "project_id": null, "relative_order": 0, "sha": null, "trailers": {} }, { "authored_date": null, "commit_author_id": null, "committed_date": null, "committer_id": null, "merge_request_commits_metadata_id": 205, "merge_request_diff_id": 107, "message": null, "project_id": null, "relative_order": 1, "sha": null, "trailers": {} }, { "authored_date": null, "commit_author_id": null, "committed_date": null, "committer_id": null, "merge_request_commits_metadata_id": 206, "merge_request_diff_id": 107, "message": null, "project_id": null, "relative_order": 2, "sha": null, "trailers": {} }, { "authored_date": null, "commit_author_id": null, "committed_date": null, "committer_id": null, "merge_request_commits_metadata_id": 207, "merge_request_diff_id": 107, "message": null, "project_id": null, "relative_order": 3, "sha": null, "trailers": {} }, { "authored_date": null, "commit_author_id": null, "committed_date": null, "committer_id": null, "merge_request_commits_metadata_id": 208, "merge_request_diff_id": 107, "message": null, "project_id": null, "relative_order": 4, "sha": null, "trailers": {} }, { "authored_date": null, "commit_author_id": null, "committed_date": null, "committer_id": null, "merge_request_commits_metadata_id": 209, "merge_request_diff_id": 107, "message": null, "project_id": null, "relative_order": 5, "sha": null, "trailers": {} } ], "files": [ { "a_mode": "100644", "b_mode": "100644", "binary": false, "deleted_file": false, "diff": "@@ -2,7 +2,10 @@\n \t@if (isLoading()) {\n \t\t
\n \t\t\t
\n-\t\t\t\t\n+\t\t\t\t
\n+\t\t\t\t\t\n+\t\t\t\t\t
{{ loadingProgress() }}%
\n+\t\t\t\t
\n \t\t\t\t

Calcul en cours...

\n \t\t\t
\n \t\t
\n", "encoded_file_path": false, "external_diff_offset": null, "external_diff_size": null, "generated": false, "merge_request_diff_id": 107, "new_file": false, "new_path": null, "old_path": "src/app/components/calendar-year/calendar-year.html", "project_id": 35, "relative_order": 0, "renamed_file": false, "too_large": false }, { "a_mode": "100644", "b_mode": "100644", "binary": false, "deleted_file": false, "diff": "@@ -51,25 +51,32 @@\n \t}\n }\n \n-.card {\n-\tmargin: 16px;\n-}\n+.spinner-container {\n+\tposition: relative;\n+\tdisplay: inline-block;\n \n-@keyframes spin {\n-\t0% {\n-\t\ttransform: rotate(0deg);\n+\t::ng-deep .mat-mdc-progress-spinner {\n+\t\t// Override Material spinner size\n+\t\twidth: 100px;\n+\t\theight: 100px;\n \t}\n \n-\t100% {\n-\t\ttransform: rotate(360deg);\n+\t.spinner-text {\n+\t\tposition: absolute;\n+\t\ttop: 50%;\n+\t\tleft: 50%;\n+\t\ttransform: translate(-50%, -50%);\n+\t\tfont-size: 20px;\n+\t\tfont-weight: bold;\n+\t\tcolor: #333;\n+\t\tpointer-events: none;\n+\n+\t\t@media (prefers-color-scheme: dark) {\n+\t\t\tcolor: #ddd;\n+\t\t}\n \t}\n }\n \n-:root {\n-\t@include mat.progress-spinner-overrides(\n-\t\t(\n-\t\t\tsize: 0.5rem,\n-\t\t\tactive-indicator-width: 0.25rem,\n-\t\t)\n-\t);\n+.card {\n+\tmargin: 16px;\n }\n", "encoded_file_path": false, "external_diff_offset": null, "external_diff_size": null, "generated": false, "merge_request_diff_id": 107, "new_file": false, "new_path": null, "old_path": "src/app/components/calendar-year/calendar-year.scss", "project_id": 35, "relative_order": 1, "renamed_file": false, "too_large": false }, { "a_mode": "100644", "b_mode": "100644", "binary": false, "deleted_file": false, "diff": "@@ -21,6 +21,7 @@ export class CalendarYear {\n \tprivate calendarSettingsService = inject(CalendarSettingsService);\n \n \tprotected isLoading = signal(true);\n+\tprotected loadingProgress = signal(0);\n \n \t// Use DateCacheService instead of creating Date objects\n \tprivate get currentMonth() {\n@@ -39,18 +40,26 @@ export class CalendarYear {\n \t\t\tconsole.log('Samedi Malin setting:', c);\n \t\t\tconsole.log('Optimizing vacations with data:', vacationData);\n \t\t\tthis.isLoading.set(true);\n-\t\t\t// Simulate server-side calculation with a small delay\n-\t\t\tsetTimeout(() => {\n-\t\t\t\tif (this.selectedDates) {\n-\t\t\t\t\t// Create a copy to avoid mutating the original signal data\n-\t\t\t\t\tthis.selectedDates.optimizeVacations(\n-\t\t\t\t\t\t{ ...vacationData },\n-\t\t\t\t\t\tthis.calendarSettingsService,\n-\t\t\t\t\t\tthis.calendarService,\n-\t\t\t\t\t);\n-\t\t\t\t}\n-\t\t\t\tthis.isLoading.set(false);\n-\t\t\t}, 1);\n+\t\t\tthis.loadingProgress.set(0);\n+\t\t\t// Defer execution to allow UI to update\n+\t\t\tqueueMicrotask(() => {\n+\t\t\t\tvoid (async () => {\n+\t\t\t\t\tif (this.selectedDates) {\n+\t\t\t\t\t\t// Create a copy to avoid mutating the original signal data\n+\t\t\t\t\t\tconst remainingDays = await this.selectedDates.optimizeVacations(\n+\t\t\t\t\t\t\t{ ...vacationData },\n+\t\t\t\t\t\t\tthis.calendarSettingsService,\n+\t\t\t\t\t\t\tthis.calendarService,\n+\t\t\t\t\t\t\t(progress: number) => {\n+\t\t\t\t\t\t\t\tthis.loadingProgress.set(progress);\n+\t\t\t\t\t\t\t},\n+\t\t\t\t\t\t);\n+\t\t\t\t\t\t// Update the remaining vacation days signal\n+\t\t\t\t\t\tthis.userInput.remainingVacationDaysSignal.set(remainingDays);\n+\t\t\t\t\t}\n+\t\t\t\t\tthis.isLoading.set(false);\n+\t\t\t\t})();\n+\t\t\t});\n \t\t});\n \t}\n \n", "encoded_file_path": false, "external_diff_offset": null, "external_diff_size": null, "generated": false, "merge_request_diff_id": 107, "new_file": false, "new_path": null, "old_path": "src/app/components/calendar-year/calendar-year.ts", "project_id": 35, "relative_order": 2, "renamed_file": false, "too_large": false }, { "a_mode": "100644", "b_mode": "100644", "binary": false, "deleted_file": false, "diff": "@@ -14,5 +14,16 @@\n \t\t\t\n \t\t\n \t\n+\t@if (hasRemainingDays()) {\n+\t\t\n+\t\t\t\n+\t\t\t\twarning\n+\t\t\t\t\n+\t\t\t\t\t{{ remainingDays() }} jour{{ remainingDays() > 1 ? 's' : '' }} de vacances non\n+\t\t\t\t\tplacé{{ remainingDays() > 1 ? 's' : '' }}. Veuillez les placer manuellement.\n+\t\t\t\t\n+\t\t\t\n+\t\t\n+\t}\n \t\n \n", "encoded_file_path": false, "external_diff_offset": null, "external_diff_size": null, "generated": false, "merge_request_diff_id": 107, "new_file": false, "new_path": null, "old_path": "src/app/components/user-input/user-input.html", "project_id": 35, "relative_order": 3, "renamed_file": false, "too_large": false }, { "a_mode": "100644", "b_mode": "100644", "binary": false, "deleted_file": false, "diff": "@@ -13,6 +13,16 @@\n \t\tfont-size: 0.875rem;\n \t\tmargin-top: 0.25rem;\n \t}\n+\n+\t.warning-card {\n+\t\tmargin-bottom: 1rem;\n+\t}\n+\n+\t.warning-content {\n+\t\tdisplay: flex;\n+\t\talign-items: center;\n+\t\tgap: 0.5rem;\n+\t}\n }\n \n form {\n", "encoded_file_path": false, "external_diff_offset": null, "external_diff_size": null, "generated": false, "merge_request_diff_id": 107, "new_file": false, "new_path": null, "old_path": "src/app/components/user-input/user-input.scss", "project_id": 35, "relative_order": 4, "renamed_file": false, "too_large": false }, { "a_mode": "100644", "b_mode": "100644", "binary": false, "deleted_file": false, "diff": "@@ -1,4 +1,4 @@\n-import { Component, OnInit, OnDestroy, effect } from '@angular/core';\n+import { Component, OnInit, OnDestroy, effect, computed } from '@angular/core';\n import { MatFormField, MatLabel } from '@angular/material/form-field';\n import { MatInputModule } from '@angular/material/input';\n import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';\n@@ -6,6 +6,8 @@ import { UserInputService } from '../../services/user-input-service';\n import { VacationsNumber } from '../../services/calendar-service';\n import { debounceTime, takeUntil, Subject } from 'rxjs';\n import { CalendarSettings } from '../calendar-settings/calendar-settings';\n+import { MatCard, MatCardContent } from '@angular/material/card';\n+import { MatIcon } from '@angular/material/icon';\n \n @Component({\n \tselector: 'app-user-input',\n@@ -15,7 +17,9 @@ import { CalendarSettings } from '../calendar-settings/calendar-settings';\n \t\tMatInputModule,\n \t\tReactiveFormsModule,\n \t\tCalendarSettings,\n-\t\tCalendarSettings,\n+\t\tMatCard,\n+\t\tMatCardContent,\n+\t\tMatIcon,\n \t],\n \ttemplateUrl: './user-input.html',\n \tstyleUrl: './user-input.scss',\n@@ -44,6 +48,13 @@ export class UserInput implements OnInit, OnDestroy {\n \t\tOthers: new FormControl(null),\n \t});\n \n+\tprotected remainingDays = computed(() => {\n+\t\tconst remaining = this.userInputService.remainingVacationDaysSignal();\n+\t\treturn remaining.cp + remaining.rtt + remaining.other;\n+\t});\n+\n+\tprotected hasRemainingDays = computed(() => this.remainingDays() > 0);\n+\n \tngOnInit() {\n \t\tthis.userInputForm.valueChanges\n \t\t\t.pipe(debounceTime(1500), takeUntil(this.destroy$))\n", "encoded_file_path": false, "external_diff_offset": null, "external_diff_size": null, "generated": false, "merge_request_diff_id": 107, "new_file": false, "new_path": null, "old_path": "src/app/components/user-input/user-input.ts", "project_id": 35, "relative_order": 5, "renamed_file": false, "too_large": false }, { "a_mode": "100644", "b_mode": "100644", "binary": false, "deleted_file": false, "diff": "@@ -298,6 +298,7 @@ export class SelectedDates implements SelectedDateInterface {\n \n \t// Constant to indicate no valid strategy was found\n \tprivate static readonly NO_VALID_STRATEGY_HEURISTIC = -1;\n+\tprivate static readonly MAX_DAYS_WITH_SAME_HEURISTIC = 9;\n \n \tupdate(): void {\n \t\tthis.datesSelected = this._datesSelected.slice();\n@@ -501,7 +502,7 @@ export class SelectedDates implements SelectedDateInterface {\n \n \t\t\t// Check if adjacent (next day) - this is the best case!\n \t\t\tif (nextStartTime - currentEndTime === oneDayInMs) {\n-\t\t\t\ttotalScore += 10; // Big bonus for adjacency\n+\t\t\t\ttotalScore += 15; // 50% increased bonus for adjacency; adjacency is highly valued in the scoring system\n \t\t\t} else if (nextStartTime > currentEndTime) {\n \t\t\t\t// Penalty based on days between the two dates\n \t\t\t\tconst daysBetween = Math.floor((nextStartTime - currentEndTime) / oneDayInMs) - 1;\n@@ -535,18 +536,43 @@ export class SelectedDates implements SelectedDateInterface {\n \t\treturn totalScore;\n \t}\n \n-\toptimizeVacations(\n+\tasync optimizeVacations(\n \t\tvacationsNumber: VacationsNumber,\n \t\tcalendarSettingsService: CalendarSettingsService,\n \t\tcalendarService: CalendarService,\n-\t): void {\n+\t\tprogressCallback?: (progress: number) => void,\n+\t): Promise {\n \t\tif (calendarSettingsService.get('samediMalin')()) this.samediMalin(vacationsNumber);\n+\n+\t\tconst initialVacations = { ...vacationsNumber };\n+\t\tconst totalDays = initialVacations.cp + initialVacations.rtt + initialVacations.other;\n+\t\tlet processedDays = 0;\n+\n+\t\t// Helper function to update progress, throttled to every 5%\n+\t\tlet lastReportedProgress = 0;\n+\t\tconst PROGRESS_STEP = 5; // percent\n+\t\tconst updateProgress = async () => {\n+\t\t\tif (progressCallback && totalDays > 0) {\n+\t\t\t\tconst progress = Math.round((processedDays / totalDays) * 100);\n+\t\t\t\tif (progress - lastReportedProgress >= PROGRESS_STEP || progress === 100) {\n+\t\t\t\t\tprogressCallback(progress);\n+\t\t\t\t\tlastReportedProgress = progress;\n+\t\t\t\t\t// Allow UI to update with a microtask\n+\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 0));\n+\t\t\t\t}\n+\t\t\t}\n+\t\t};\n+\n+\t\tawait updateProgress();\n+\n \t\twhile (vacationsNumber.cp > 0) {\n \t\t\tconst result = this.lookForVacation(DayType.CP, calendarService);\n \t\t\tif (result.heuristic === SelectedDates.NO_VALID_STRATEGY_HEURISTIC) {\n \t\t\t\tbreak;\n \t\t\t}\n \t\t\tvacationsNumber.cp -= 1;\n+\t\t\tprocessedDays++;\n+\t\t\tawait updateProgress();\n \t\t}\n \t\twhile (vacationsNumber.rtt > 0) {\n \t\t\tconst result = this.lookForVacation(DayType.RTT, calendarService);\n@@ -554,6 +580,8 @@ export class SelectedDates implements SelectedDateInterface {\n \t\t\t\tbreak;\n \t\t\t}\n \t\t\tvacationsNumber.rtt -= 1;\n+\t\t\tprocessedDays++;\n+\t\t\tawait updateProgress();\n \t\t}\n \t\twhile (vacationsNumber.other > 0) {\n \t\t\tconst result = this.lookForVacation(DayType.OTHER, calendarService);\n@@ -561,7 +589,25 @@ export class SelectedDates implements SelectedDateInterface {\n \t\t\t\tbreak;\n \t\t\t}\n \t\t\tvacationsNumber.other -= 1;\n+\t\t\tprocessedDays++;\n+\t\t\tawait updateProgress();\n \t\t}\n+\n+\t\t// Final progress update (100%)\n+\t\tif (progressCallback) {\n+\t\t\tprogressCallback(100);\n+\t\t}\n+\n+\t\t// Inform user if not all days were placed\n+\t\tconst remainingDays = vacationsNumber.cp + vacationsNumber.rtt + vacationsNumber.other;\n+\t\tif (remainingDays > 0) {\n+\t\t\tconsole.warn(\n+\t\t\t\t`Optimization stopped: ${remainingDays} of ${totalDays} vacation days remain unplaced. ` +\n+\t\t\t\t\t`Too many days have equivalent placement quality - please place remaining days manually.`,\n+\t\t\t);\n+\t\t}\n+\n+\t\treturn vacationsNumber;\n \t}\n \n \tsamediMalin(vacationsNumber: VacationsNumber): void {\n@@ -751,6 +797,7 @@ export class SelectedDates implements SelectedDateInterface {\n \t\tconst tempDatesBackup = [...this._datesSelected];\n \t\tlet bestStrategy: { apply: () => void; heuristic: number; value: Date } | null = null;\n \t\tlet bestHeuristic = -Infinity;\n+\t\tlet daysWithBestHeuristic = 0;\n \n \t\t// Evaluate each candidate day\n \t\tfor (const day of candidateDays) {\n@@ -767,6 +814,7 @@ export class SelectedDates implements SelectedDateInterface {\n \t\t\t// Track only if better than current best\n \t\t\tif (heuristic > bestHeuristic) {\n \t\t\t\tbestHeuristic = heuristic;\n+\t\t\t\tdaysWithBestHeuristic = 1;\n \t\t\t\tconst capturedDay = day;\n \t\t\t\tbestStrategy = {\n \t\t\t\t\tapply: () => {\n@@ -781,9 +829,25 @@ export class SelectedDates implements SelectedDateInterface {\n \t\t\t\t\theuristic: heuristic,\n \t\t\t\t\tvalue: capturedDay,\n \t\t\t\t};\n+\t\t\t} else if (heuristic === bestHeuristic) {\n+\t\t\t\tdaysWithBestHeuristic++;\n \t\t\t}\n \t\t}\n \n+\t\t// Check if too many days have the same best heuristic after evaluating all days\n+\t\tif (daysWithBestHeuristic > SelectedDates.MAX_DAYS_WITH_SAME_HEURISTIC) {\n+\t\t\t// Restore backup before stopping\n+\t\t\tthis._datesSelected = [...tempDatesBackup];\n+\t\t\tthis.datesSelected = this._datesSelected.slice();\n+\t\t\tthis.grouping();\n+\n+\t\t\t// Stop optimization - let user decide manually\n+\t\t\treturn {\n+\t\t\t\tapply: () => {},\n+\t\t\t\theuristic: SelectedDates.NO_VALID_STRATEGY_HEURISTIC,\n+\t\t\t};\n+\t\t}\n+\n \t\t// Restore backup before returning\n \t\tthis._datesSelected = [...tempDatesBackup];\n \t\tthis.datesSelected = this._datesSelected.slice();\n", "encoded_file_path": false, "external_diff_offset": null, "external_diff_size": null, "generated": false, "merge_request_diff_id": 107, "new_file": false, "new_path": null, "old_path": "src/app/services/calendar-service.ts", "project_id": 35, "relative_order": 6, "renamed_file": false, "too_large": false }, { "a_mode": "100644", "b_mode": "100644", "binary": false, "deleted_file": false, "diff": "@@ -18,4 +18,9 @@ export class UserInputService {\n \t}\n \n \treadonly vacationNumberSignal = signal(this.getInitialValues());\n+\treadonly remainingVacationDaysSignal = signal({\n+\t\tcp: 0,\n+\t\trtt: 0,\n+\t\tother: 0,\n+\t});\n }\n", "encoded_file_path": false, "external_diff_offset": null, "external_diff_size": null, "generated": false, "merge_request_diff_id": 107, "new_file": false, "new_path": null, "old_path": "src/app/services/user-input-service.ts", "project_id": 35, "relative_order": 7, "renamed_file": false, "too_large": false } ], "metadata": { "base_commit_sha": "2d5cccff0ceac6890fb06c650e5aa078c1cf6b44", "commits_count": 6, "created_at": "2026-05-03T15:59:55.89964", "diff_type": 1, "files_count": 8, "head_commit_sha": "54c2160f0dd293f8c1c78342e155a65d891041fd", "id": 107, "merge_request_id": 105, "project_id": 35, "start_commit_sha": "2d5cccff0ceac6890fb06c650e5aa078c1cf6b44", "state": "collected", "stored_externally": null, "updated_at": "2026-05-03T15:59:56.261067" } } ], "discussions": [ { "id": "ab40fc34d9f56f17f5e596b9b7c419d248ba6333", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "assigned to `@Copilot`", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:14:45.000Z", "id": 462, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": true, "type": null, "updated_at": "2025-11-17T23:14:45.000Z" } ], "resolvable": false }, { "id": "f578eb263228a77c13e8d3910246ff43533d69c1", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "requested review from `@Copilot`", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:14:46.000Z", "id": 464, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": true, "type": null, "updated_at": "2025-11-17T23:14:46.000Z" } ], "resolvable": false }, { "id": "b19de6e4f1636ee4cb5864d6f8d7dc8ced3c75b9", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "closes #16 ", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:15:14.000Z", "id": 463, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": null, "updated_at": "2025-11-17T23:15:14.000Z" } ], "resolvable": false }, { "id": "9dfffc1a8aa9b415bdbc220d95dd91cd1066051a", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "Wrapping an async function in setTimeout is an anti-pattern. The setTimeout callback should remain synchronous, and the async operations should be handled with proper promise chaining or await. Consider restructuring this to use `setTimeout(() => { void asyncFunction(); })` or better yet, replace setTimeout with a proper async delay mechanism.", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:17:13.000Z", "id": 268, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:15.000Z" }, { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "`@copilot` open a new pull request to apply changes based on [this feedback](https://gitlab.nessar.fr/Nessar/vac-optimizer/-/merge_requests/49#discussion_r2535792120)", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:17:34.000Z", "id": 272, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:34.000Z" } ], "resolvable": false }, { "id": "5a8609f26b8a5468866f450e2c0f5eb0b7d38ace", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "Using `setTimeout(resolve, 0)` on every iteration (potentially hundreds of times) will significantly degrade performance. This creates unnecessary macrotasks and event loop overhead. Consider updating progress less frequently (e.g., every 10 iterations or based on a time threshold) or using `requestAnimationFrame` for UI updates.", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:17:14.000Z", "id": 269, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:15.000Z" } ], "resolvable": false }, { "id": "8e7a85b15e186a4fdcb15dbd98f89114897bab1b", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "The constant `MAX_DAYS_WITH_SAME_HEURISTIC` (value 9) lacks documentation explaining why this specific threshold was chosen. Add a comment explaining the rationale behind this magic number to improve maintainability.", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:17:14.000Z", "id": 270, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:16.000Z" } ], "resolvable": false }, { "id": "e91c4615decb79d83535a8c18d36bbf45bb527ff", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "[nitpick] The comment should be updated to reflect the new value. It now says 'Big bonus' but the change from 10 to 15 represents a 50% increase. Consider updating the comment to explain why this specific value was chosen or remove the generic 'Big bonus' comment if the value speaks for itself.\n```suggestion:-0+0\n\t\t\t\ttotalScore += 15; // 50% increased bonus for adjacency; adjacency is highly valued in the scoring system\n```", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:17:14.000Z", "id": 275, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:15.000Z" } ], "resolvable": false }, { "id": "93fb552a5fe24e7b2cbd44fa7262d56536b3af26", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "Using `!important` to override Material Design styles is an anti-pattern. Consider using a more specific CSS selector or Angular's ViewEncapsulation to properly scope styles without relying on `!important`.\n```suggestion:-0+0\n\t\twidth: 100px;\n\t\theight: 100px;\n```", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:17:14.000Z", "id": 276, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:16.000Z" } ], "resolvable": false }, { "id": "d13eff09204c438822ce9d9d36b07858ef3d1403", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "[nitpick] Console warnings in production code should be avoided or wrapped in a logging service. Consider using a proper logging service that can be configured for different environments (development vs production).", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:17:15.000Z", "id": 271, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:16.000Z" }, { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "`@copilot` open a new pull request to apply changes based on [this feedback](https://gitlab.nessar.fr/Nessar/vac-optimizer/-/merge_requests/49#r2535792156);\r\n\r\nalso remove constructor injection and use inject()", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:28:08.000Z", "id": 277, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:29:57.000Z" } ], "resolvable": false }, { "id": "1c8f21f72999404b53060774fb1e690285b8af5d", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "**Review:** Commented\n\n## Pull Request Overview\n\nThis PR enhances the vacation optimization feature by adding visual progress feedback during optimization and displaying warnings when vacation days cannot be placed automatically. The optimization algorithm has been made asynchronous to allow UI updates during processing.\n\n### Key Changes\n- Made vacation optimization asynchronous with progress callbacks\n- Added remaining vacation days tracking with a warning UI\n- Enhanced heuristic logic to stop optimization when too many days have equal placement quality\n\n### Reviewed Changes\n\nCopilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.\n\n
\nShow a summary per file\n\n| File | Description |\r\n| ---- | ----------- |\r\n| src/app/services/user-input-service.ts | Added signal to track remaining unplaced vacation days |\r\n| src/app/services/calendar-service.ts | Converted optimization to async, added progress tracking and stop condition for ambiguous placements |\r\n| src/app/components/user-input/user-input.ts | Added computed properties to display remaining vacation days warning |\r\n| src/app/components/user-input/user-input.html | Added warning card UI for unplaced vacation days |\r\n| src/app/components/user-input/user-input.scss | Added styles for warning card component |\r\n| src/app/components/calendar-year/calendar-year.ts | Integrated progress tracking with loading spinner |\r\n| src/app/components/calendar-year/calendar-year.html | Updated spinner to show progress percentage |\r\n| src/app/components/calendar-year/calendar-year.scss | Styled progress spinner with percentage overlay |\n
\n\n\n\n\n", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:17:15.000Z", "id": 466, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": null, "updated_at": "2025-11-17T23:17:15.000Z" } ], "resolvable": false }, { "id": "8b5352e07c984620dd3bf079ccba4fb401ce73c8", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "`@THoguet` I've opened a new pull request, #50, to work on those changes. Once the pull request is ready, I'll request review from you.", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:17:41.000Z", "id": 465, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": null, "updated_at": "2025-11-17T23:17:41.000Z" } ], "resolvable": false }, { "id": "4af972d5943542400285efdbe536ff5d4562b39c", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "mentioned in merge request !50", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:17:41.000Z", "id": 470, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": true, "type": null, "updated_at": "2026-05-03T16:02:15.491Z" } ], "resolvable": false }, { "id": "f3ab5d0fcfc131027c5345971729820e6ab00483", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "requested review from `@Copilot`", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:18:12.000Z", "id": 467, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": true, "type": null, "updated_at": "2025-11-17T23:18:12.000Z" } ], "resolvable": false }, { "id": "5e7a99d7d4d9264fe8c0277946d04234ef653223", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "The magic number 9 for MAX_DAYS_WITH_SAME_HEURISTIC lacks explanation. Add a comment explaining why 9 is the threshold (e.g., based on testing, represents approximately one week of vacation days, or other reasoning).\n```suggestion:-0+0\n\tprivate static readonly NO_VALID_STRATEGY_HEURISTIC = -1;\n\t// MAX_DAYS_WITH_SAME_HEURISTIC is set to 9 to represent approximately one week of vacation days (5 workdays + 2 weekends) plus a buffer for common extended leave patterns, based on user testing and business requirements.\n```", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:20:17.000Z", "id": 278, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:20:18.000Z" } ], "resolvable": false }, { "id": "d2529a35a6dc1bcf6b3d0bcb571b6ba5f9278ffd", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "Using `setTimeout(resolve, 0)` in a potentially tight loop during optimization could significantly slow down the process. For a vacation with 20+ days, this adds 20+ microtask delays. Consider throttling progress updates (e.g., only update every 5% or every N days) instead of on every iteration.\n```suggestion:-0+0\n\t\t// Helper function to update progress, throttled to every 5%\n\t\tlet lastReportedProgress = 0;\n\t\tconst PROGRESS_STEP = 5; // percent\n\t\tconst updateProgress = async () => {\n\t\t\tif (progressCallback && totalDays > 0) {\n\t\t\t\tconst progress = Math.round((processedDays / totalDays) * 100);\n\t\t\t\tif (progress - lastReportedProgress >= PROGRESS_STEP || progress === 100) {\n\t\t\t\t\tprogressCallback(progress);\n\t\t\t\t\tlastReportedProgress = progress;\n\t\t\t\t\t// Allow UI to update with a microtask\n\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 0));\n\t\t\t\t}\n```", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:20:17.000Z", "id": 279, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:20:18.000Z" } ], "resolvable": false }, { "id": "6e6edb5e5a6d69df10aff38df952870affded28f", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "The warning icon lacks an aria-label or other accessible text for screen readers. Add `aria-label=\\\"Warning\\\"` or use `aria-hidden=\\\"true\\\"` if the text description is sufficient.\n```suggestion:-0+0\n\t\t\t\twarning\n```", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:20:17.000Z", "id": 280, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:20:18.000Z" } ], "resolvable": false }, { "id": "458c95de59b025c2aebd63d4d0c62a2cdbc31c44", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "**Review:** Commented\n\n## Pull Request Overview\n\nCopilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.\n\n\n
\nComments suppressed due to low confidence (1)\n\n**src/app/components/user-input/user-input.ts:28**\n* The component uses constructor injection for `UserInputService`, but the coding guidelines specify using the `inject()` function instead. Consider changing to `private userInputService = inject(UserInputService);` at the class level for consistency with Angular best practices.\n```\n\tconstructor(private userInputService: UserInputService) {\n```\n
\n\n", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:20:18.000Z", "id": 471, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": null, "updated_at": "2025-11-17T23:20:18.000Z" } ], "resolvable": false }, { "id": "c07cbab837fdd606b507af0a647c40d706cfbe23", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "requested review from `@Copilot`", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:21:42.000Z", "id": 468, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": true, "type": null, "updated_at": "2025-11-17T23:21:42.000Z" } ], "resolvable": false }, { "id": "b525b494651e334ed6aa24b907701bc229cce756", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "`@THoguet` I've opened a new pull request, #51, to work on those changes. Once the pull request is ready, I'll request review from you.", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:30:05.000Z", "id": 469, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": null, "updated_at": "2025-11-17T23:30:05.000Z" } ], "resolvable": false }, { "id": "e1ccb8c00f47780df1b02fa50f6cc9fc4c9ef49f", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "mentioned in merge request !51", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:30:05.000Z", "id": 474, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": true, "type": null, "updated_at": "2026-05-03T16:02:15.662Z" } ], "resolvable": false }, { "id": "8bc9f7406f79b1604b9c259d1aab3331b8fddc93", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "The spinner should have an accessible label to inform screen reader users about the loading state and progress. The `mat-spinner` component should include `aria-label` or be wrapped in an element with an appropriate label.\n\nAdd an `aria-label` to the spinner:\n```html\n\n\n```\n```suggestion:-0+0\n\t\t\t\t\t\n\t\t\t\t\t\n```", "commands_changes": {}, "commit_id": "54c2160f0dd293f8c1c78342e155a65d891041fd", "confidential": false, "created_at": "2025-11-17T23:35:59.000Z", "id": 281, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "position": { "base_sha": "2d5cccff0ceac6890fb06c650e5aa078c1cf6b44", "head_sha": "54c2160f0dd293f8c1c78342e155a65d891041fd", "line_range": null, "new_line": 6, "new_path": "src/app/components/calendar-year/calendar-year.html", "old_line": null, "old_path": "src/app/components/calendar-year/calendar-year.html", "position_type": "text", "start_sha": "2d5cccff0ceac6890fb06c650e5aa078c1cf6b44" }, "project_id": 35, "resolvable": true, "resolved": false, "resolved_at": null, "resolved_by": null, "suggestions": [ { "appliable": false, "applied": false, "from_content": "\t\t\t\t\t\n", "from_line": 6, "id": 5, "to_content": "\t\t\t\t\t\n\t\t\t\t\t\n", "to_line": 6 } ], "system": false, "type": "DiffNote", "updated_at": "2025-11-17T23:36:00.000Z" } ], "resolvable": true, "resolved": false }, { "id": "72e1b08bd14eb0a3e1ad3cf8b31d9dcac81b0eb3", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "Using `setTimeout(resolve, 0)` for every single day placement could cause performance issues when optimizing a large number of vacation days. Each `setTimeout` adds overhead and forces the event loop to cycle unnecessarily.\n\nConsider batching progress updates or using a minimum time interval between updates:\n```typescript\nconst updateProgress = async () => {\n\tif (progressCallback && totalDays > 0) {\n\t\tconst progress = Math.round((processedDays / totalDays) * 100);\n\t\t// Only update if progress changed by at least 1%\n\t\tif (progress !== this.lastProgress) {\n\t\t\tprogressCallback(progress);\n\t\t\tthis.lastProgress = progress;\n\t\t\t// Allow UI to update with a microtask\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 0));\n\t\t}\n\t}\n};\n```\n\nOr update progress only every N days:\n```typescript\nif (processedDays % 5 === 0 || processedDays === totalDays) {\n\tawait updateProgress();\n}\n```\n```suggestion:-7+0\n\t\tlet lastYieldTime = Date.now();\n\t\tconst YIELD_INTERVAL_MS = 100;\n\t\tconst updateProgress = async () => {\n\t\t\tif (progressCallback && totalDays > 0) {\n\t\t\t\tconst progress = Math.round((processedDays / totalDays) * 100);\n\t\t\t\tif (progress - lastReportedProgress >= PROGRESS_STEP || progress === 100) {\n\t\t\t\t\tprogressCallback(progress);\n\t\t\t\t\tlastReportedProgress = progress;\n\t\t\t\t\tconst now = Date.now();\n\t\t\t\t\tif (now - lastYieldTime >= YIELD_INTERVAL_MS) {\n\t\t\t\t\t\tlastYieldTime = now;\n\t\t\t\t\t\t// Allow UI to update with a microtask\n\t\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 0));\n\t\t\t\t\t}\n```", "commands_changes": {}, "commit_id": "54c2160f0dd293f8c1c78342e155a65d891041fd", "confidential": false, "created_at": "2025-11-17T23:35:59.000Z", "id": 282, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "position": { "base_sha": "2d5cccff0ceac6890fb06c650e5aa078c1cf6b44", "head_sha": "54c2160f0dd293f8c1c78342e155a65d891041fd", "line_range": null, "new_line": 561, "new_path": "src/app/services/calendar-service.ts", "old_line": null, "old_path": "src/app/services/calendar-service.ts", "position_type": "text", "start_sha": "2d5cccff0ceac6890fb06c650e5aa078c1cf6b44" }, "project_id": 35, "resolvable": true, "resolved": false, "resolved_at": null, "resolved_by": null, "suggestions": [ { "appliable": false, "applied": false, "from_content": "\t\tconst updateProgress = async () => {\n\t\t\tif (progressCallback && totalDays > 0) {\n\t\t\t\tconst progress = Math.round((processedDays / totalDays) * 100);\n\t\t\t\tif (progress - lastReportedProgress >= PROGRESS_STEP || progress === 100) {\n\t\t\t\t\tprogressCallback(progress);\n\t\t\t\t\tlastReportedProgress = progress;\n\t\t\t\t\t// Allow UI to update with a microtask\n\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 0));\n", "from_line": 554, "id": 6, "to_content": "\t\tlet lastYieldTime = Date.now();\n\t\tconst YIELD_INTERVAL_MS = 100;\n\t\tconst updateProgress = async () => {\n\t\t\tif (progressCallback && totalDays > 0) {\n\t\t\t\tconst progress = Math.round((processedDays / totalDays) * 100);\n\t\t\t\tif (progress - lastReportedProgress >= PROGRESS_STEP || progress === 100) {\n\t\t\t\t\tprogressCallback(progress);\n\t\t\t\t\tlastReportedProgress = progress;\n\t\t\t\t\tconst now = Date.now();\n\t\t\t\t\tif (now - lastYieldTime >= YIELD_INTERVAL_MS) {\n\t\t\t\t\t\tlastYieldTime = now;\n\t\t\t\t\t\t// Allow UI to update with a microtask\n\t\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 0));\n\t\t\t\t\t}\n", "to_line": 561 } ], "system": false, "type": "DiffNote", "updated_at": "2025-11-17T23:36:01.000Z" } ], "resolvable": true, "resolved": false }, { "id": "fc64e964e194bfe173bdc399115e50b58b4ff5ba", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "The warning message should have an `aria-live` attribute to announce changes to screen reader users. When the warning appears dynamically, assistive technologies should be notified.\n\nAdd `aria-live=\"polite\"` to the `mat-card` element:\n```html\n\n```\n```suggestion:-0+0\n\t\t\n```", "commands_changes": {}, "commit_id": "54c2160f0dd293f8c1c78342e155a65d891041fd", "confidential": false, "created_at": "2025-11-17T23:35:59.000Z", "id": 289, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "position": { "base_sha": "2d5cccff0ceac6890fb06c650e5aa078c1cf6b44", "head_sha": "54c2160f0dd293f8c1c78342e155a65d891041fd", "line_range": null, "new_line": 18, "new_path": "src/app/components/user-input/user-input.html", "old_line": null, "old_path": "src/app/components/user-input/user-input.html", "position_type": "text", "start_sha": "2d5cccff0ceac6890fb06c650e5aa078c1cf6b44" }, "project_id": 35, "resolvable": true, "resolved": false, "resolved_at": null, "resolved_by": null, "suggestions": [ { "appliable": false, "applied": false, "from_content": "\t\t\n", "from_line": 18, "id": 7, "to_content": "\t\t\n", "to_line": 18 } ], "system": false, "type": "DiffNote", "updated_at": "2025-11-17T23:36:01.000Z" } ], "resolvable": true, "resolved": false }, { "id": "1a9b0e1c4f25560214390d8a636bf4034c998f66", "individual_note": false, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "[nitpick] The warning icon should have an `aria-label` to provide context for screen readers. While `aria-hidden=\"true\"` prevents the icon from being announced, the warning text should be properly associated with the icon's semantic meaning.\n\nConsider wrapping the content in a semantic element with an appropriate role or adding an `aria-label` to the parent span:\n```html\n\n\twarning\n\t\n\t\t{{ remainingDays() }} jour{{ remainingDays() > 1 ? 's' : '' }} de vacances non\n\t\tplacé{{ remainingDays() > 1 ? 's' : '' }}. Veuillez les placer manuellement.\n\t\n\n```\n```suggestion:-3+0\n\t\t\t\t\n\t\t\t\t\twarning\n\t\t\t\t\t\n\t\t\t\t\t\t{{ remainingDays() }} jour{{ remainingDays() > 1 ? 's' : '' }} de vacances non\n\t\t\t\t\t\tplacé{{ remainingDays() > 1 ? 's' : '' }}. Veuillez les placer manuellement.\n\t\t\t\t\t\n```", "commands_changes": {}, "commit_id": "54c2160f0dd293f8c1c78342e155a65d891041fd", "confidential": false, "created_at": "2025-11-17T23:36:00.000Z", "id": 291, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "position": { "base_sha": "2d5cccff0ceac6890fb06c650e5aa078c1cf6b44", "head_sha": "54c2160f0dd293f8c1c78342e155a65d891041fd", "line_range": null, "new_line": 23, "new_path": "src/app/components/user-input/user-input.html", "old_line": null, "old_path": "src/app/components/user-input/user-input.html", "position_type": "text", "start_sha": "2d5cccff0ceac6890fb06c650e5aa078c1cf6b44" }, "project_id": 35, "resolvable": true, "resolved": false, "resolved_at": null, "resolved_by": null, "suggestions": [ { "appliable": false, "applied": false, "from_content": "\t\t\t\twarning\n\t\t\t\t\n\t\t\t\t\t{{ remainingDays() }} jour{{ remainingDays() > 1 ? 's' : '' }} de vacances non\n\t\t\t\t\tplacé{{ remainingDays() > 1 ? 's' : '' }}. Veuillez les placer manuellement.\n", "from_line": 20, "id": 8, "to_content": "\t\t\t\t\n\t\t\t\t\twarning\n\t\t\t\t\t\n\t\t\t\t\t\t{{ remainingDays() }} jour{{ remainingDays() > 1 ? 's' : '' }} de vacances non\n\t\t\t\t\t\tplacé{{ remainingDays() > 1 ? 's' : '' }}. Veuillez les placer manuellement.\n\t\t\t\t\t\n", "to_line": 23 } ], "system": false, "type": "DiffNote", "updated_at": "2025-11-17T23:36:01.000Z" } ], "resolvable": true, "resolved": false }, { "id": "c3548e1b972f4b88c5637d81fd6708ad7b38a402", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "**Review:** Commented\n\n## Pull Request Overview\n\nCopilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.\n\n\n\n", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:36:00.000Z", "id": 472, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": null, "updated_at": "2025-11-17T23:36:00.000Z" } ], "resolvable": false }, { "id": "abfb43885063dbd6a02def51ab7f3264e4391538", "individual_note": true, "notes": [ { "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "body": "*Merged by: THoguet at 2025-11-17 23:45:05 UTC*", "commands_changes": {}, "confidential": false, "created_at": "2025-11-17T23:45:05.000Z", "id": 473, "imported": true, "imported_from": "github", "internal": false, "noteable_id": 105, "noteable_iid": 49, "noteable_type": "MergeRequest", "project_id": 35, "resolvable": false, "system": false, "type": null, "updated_at": "2026-05-03T16:02:15.726Z" } ], "resolvable": false } ], "format_version": 1, "notes": [ { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:14:45", "discussion_id": "ab40fc34d9f56f17f5e596b9b7c419d248ba6333", "id": 462, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "assigned to `@Copilot`", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": true, "type": null, "updated_at": "2025-11-17T23:14:45", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:14:46", "discussion_id": "f578eb263228a77c13e8d3910246ff43533d69c1", "id": 464, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "requested review from `@Copilot`", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": true, "type": null, "updated_at": "2025-11-17T23:14:46", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:15:14", "discussion_id": "b19de6e4f1636ee4cb5864d6f8d7dc8ced3c75b9", "id": 463, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "closes #16 ", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": false, "type": null, "updated_at": "2025-11-17T23:15:14", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "5ab29ef7700cb45e321469e2b5f69678a5761fff", "confidential": null, "created_at": "2025-11-17T23:17:13", "discussion_id": "9dfffc1a8aa9b415bdbc220d95dd91cd1066051a", "id": 268, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "039c4c160f861e3a0e271f37b5fff76a6ad3adcf_44_45", "namespace_id": null, "note": "Wrapping an async function in setTimeout is an anti-pattern. The setTimeout callback should remain synchronous, and the async operations should be handled with proper promise chaining or await. Consider restructuring this to use `setTimeout(() => { void asyncFunction(); })` or better yet, replace setTimeout with a proper async delay mechanism.", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": "---\n:diff: \"@@ -39,15 +40,21 @@ export class CalendarYear {\\n \\t\\t\\tconsole.log('Samedi\n Malin setting:', c);\\n \\t\\t\\tconsole.log('Optimizing vacations with data:', vacationData);\\n\n \\t\\t\\tthis.isLoading.set(true);\\n+\\t\\t\\tthis.loadingProgress.set(0);\\n \\t\\t\\t//\n Simulate server-side calculation with a small delay\\n-\\t\\t\\tsetTimeout(() => {\\n+\\t\\t\\tsetTimeout(async\n () => {\"\n:new_path: src/app/components/calendar-year/calendar-year.ts\n:old_path: src/app/components/calendar-year/calendar-year.ts\n:a_mode: '100644'\n:b_mode: '100644'\n:new_file: false\n", "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:15", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "5ab29ef7700cb45e321469e2b5f69678a5761fff", "confidential": null, "created_at": "2025-11-17T23:17:14", "discussion_id": "5a8609f26b8a5468866f450e2c0f5eb0b7d38ace", "id": 269, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "c096123e634c24e4c6dfdac15c495e299686594a_544_557", "namespace_id": null, "note": "Using `setTimeout(resolve, 0)` on every iteration (potentially hundreds of times) will significantly degrade performance. This creates unnecessary macrotasks and event loop overhead. Consider updating progress less frequently (e.g., every 10 iterations or based on a time threshold) or using `requestAnimationFrame` for UI updates.", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": "---\n:diff: \"@@ -535,33 +536,73 @@ export class SelectedDates implements SelectedDateInterface\n {\\n \\t\\treturn totalScore;\\n \\t}\\n \\n-\\toptimizeVacations(\\n+\\tasync optimizeVacations(\\n\n \\t\\tvacationsNumber: VacationsNumber,\\n \\t\\tcalendarSettingsService: CalendarSettingsService,\\n\n \\t\\tcalendarService: CalendarService,\\n-\\t): void {\\n+\\t\\tprogressCallback?: (progress:\n number) => void,\\n+\\t): Promise {\\n \\t\\tif (calendarSettingsService.get('samediMalin')())\n this.samediMalin(vacationsNumber);\\n+\\n+\\t\\tconst initialVacations = { ...vacationsNumber\n };\\n+\\t\\tconst totalDays = initialVacations.cp + initialVacations.rtt + initialVacations.other;\\n+\\t\\tlet\n processedDays = 0;\\n+\\n+\\t\\t// Helper function to update progress\\n+\\t\\tconst updateProgress\n = async () => {\\n+\\t\\t\\tif (progressCallback && totalDays > 0) {\\n+\\t\\t\\t\\tconst\n progress = Math.round((processedDays / totalDays) * 100);\\n+\\t\\t\\t\\tprogressCallback(progress);\\n+\\t\\t\\t\\t//\n Allow UI to update with a microtask\\n+\\t\\t\\t\\tawait new Promise((resolve) => setTimeout(resolve,\n 0));\"\n:new_path: src/app/services/calendar-service.ts\n:old_path: src/app/services/calendar-service.ts\n:a_mode: '100644'\n:b_mode: '100644'\n:new_file: false\n", "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:15", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "5ab29ef7700cb45e321469e2b5f69678a5761fff", "confidential": null, "created_at": "2025-11-17T23:17:14", "discussion_id": "8e7a85b15e186a4fdcb15dbd98f89114897bab1b", "id": 270, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "c096123e634c24e4c6dfdac15c495e299686594a_787_833", "namespace_id": null, "note": "The constant `MAX_DAYS_WITH_SAME_HEURISTIC` (value 9) lacks documentation explaining why this specific threshold was chosen. Add a comment explaining the rationale behind this magic number to improve maintainability.", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": "---\n:diff: \"@@ -781,9 +824,25 @@ export class SelectedDates implements SelectedDateInterface\n {\\n \\t\\t\\t\\t\\theuristic: heuristic,\\n \\t\\t\\t\\t\\tvalue: capturedDay,\\n \\t\\t\\t\\t};\\n+\\t\\t\\t}\n else if (heuristic === bestHeuristic) {\\n+\\t\\t\\t\\tdaysWithBestHeuristic++;\\n \\t\\t\\t}\\n\n \\t\\t}\\n \\n+\\t\\t// Check if too many days have the same best heuristic after evaluating\n all days\\n+\\t\\tif (daysWithBestHeuristic > SelectedDates.MAX_DAYS_WITH_SAME_HEURISTIC)\n {\"\n:new_path: src/app/services/calendar-service.ts\n:old_path: src/app/services/calendar-service.ts\n:a_mode: '100644'\n:b_mode: '100644'\n:new_file: false\n", "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:16", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "5ab29ef7700cb45e321469e2b5f69678a5761fff", "confidential": null, "created_at": "2025-11-17T23:17:14", "discussion_id": "e91c4615decb79d83535a8c18d36bbf45bb527ff", "id": 275, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "c096123e634c24e4c6dfdac15c495e299686594a_505_505", "namespace_id": null, "note": "[nitpick] The comment should be updated to reflect the new value. It now says 'Big bonus' but the change from 10 to 15 represents a 50% increase. Consider updating the comment to explain why this specific value was chosen or remove the generic 'Big bonus' comment if the value speaks for itself.\n```suggestion:-0+0\n\t\t\t\ttotalScore += 15; // 50% increased bonus for adjacency; adjacency is highly valued in the scoring system\n```", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": "---\n:diff: \"@@ -501,7 +502,7 @@ export class SelectedDates implements SelectedDateInterface\n {\\n \\n \\t\\t\\t// Check if adjacent (next day) - this is the best case!\\n \\t\\t\\tif\n (nextStartTime - currentEndTime === oneDayInMs) {\\n-\\t\\t\\t\\ttotalScore += 10; //\n Big bonus for adjacency\\n+\\t\\t\\t\\ttotalScore += 15; // Big bonus for adjacency\"\n:new_path: src/app/services/calendar-service.ts\n:old_path: src/app/services/calendar-service.ts\n:a_mode: '100644'\n:b_mode: '100644'\n:new_file: false\n", "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:15", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "5ab29ef7700cb45e321469e2b5f69678a5761fff", "confidential": null, "created_at": "2025-11-17T23:17:14", "discussion_id": "93fb552a5fe24e7b2cbd44fa7262d56536b3af26", "id": 276, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "7a47b7c3865387ec9d0191938c8b5a64ad9b870f_61_61", "namespace_id": null, "note": "Using `!important` to override Material Design styles is an anti-pattern. Consider using a more specific CSS selector or Angular's ViewEncapsulation to properly scope styles without relying on `!important`.\n```suggestion:-0+0\n\t\twidth: 100px;\n\t\theight: 100px;\n```", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": "---\n:diff: \"@@ -51,25 +51,32 @@\\n \\t}\\n }\\n \\n-.card {\\n-\\tmargin: 16px;\\n-}\\n+.spinner-container\n {\\n+\\tposition: relative;\\n+\\tdisplay: inline-block;\\n \\n-@keyframes spin {\\n-\\t0%\n {\\n-\\t\\ttransform: rotate(0deg);\\n+\\t::ng-deep .mat-mdc-progress-spinner {\\n+\\t\\t//\n Override Material spinner size\\n+\\t\\twidth: 100px !important;\\n+\\t\\theight: 100px\n !important;\"\n:new_path: src/app/components/calendar-year/calendar-year.scss\n:old_path: src/app/components/calendar-year/calendar-year.scss\n:a_mode: '100644'\n:b_mode: '100644'\n:new_file: false\n", "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:16", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "5ab29ef7700cb45e321469e2b5f69678a5761fff", "confidential": null, "created_at": "2025-11-17T23:17:15", "discussion_id": "d13eff09204c438822ce9d9d36b07858ef3d1403", "id": 271, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "c096123e634c24e4c6dfdac15c495e299686594a_564_602", "namespace_id": null, "note": "[nitpick] Console warnings in production code should be avoided or wrapped in a logging service. Consider using a proper logging service that can be configured for different environments (development vs production).", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": "---\n:diff: \"@@ -535,33 +536,73 @@ export class SelectedDates implements SelectedDateInterface\n {\\n \\t\\treturn totalScore;\\n \\t}\\n \\n-\\toptimizeVacations(\\n+\\tasync optimizeVacations(\\n\n \\t\\tvacationsNumber: VacationsNumber,\\n \\t\\tcalendarSettingsService: CalendarSettingsService,\\n\n \\t\\tcalendarService: CalendarService,\\n-\\t): void {\\n+\\t\\tprogressCallback?: (progress:\n number) => void,\\n+\\t): Promise {\\n \\t\\tif (calendarSettingsService.get('samediMalin')())\n this.samediMalin(vacationsNumber);\\n+\\n+\\t\\tconst initialVacations = { ...vacationsNumber\n };\\n+\\t\\tconst totalDays = initialVacations.cp + initialVacations.rtt + initialVacations.other;\\n+\\t\\tlet\n processedDays = 0;\\n+\\n+\\t\\t// Helper function to update progress\\n+\\t\\tconst updateProgress\n = async () => {\\n+\\t\\t\\tif (progressCallback && totalDays > 0) {\\n+\\t\\t\\t\\tconst\n progress = Math.round((processedDays / totalDays) * 100);\\n+\\t\\t\\t\\tprogressCallback(progress);\\n+\\t\\t\\t\\t//\n Allow UI to update with a microtask\\n+\\t\\t\\t\\tawait new Promise((resolve) => setTimeout(resolve,\n 0));\\n+\\t\\t\\t}\\n+\\t\\t};\\n+\\n+\\t\\tawait updateProgress();\\n+\\n \\t\\twhile (vacationsNumber.cp\n > 0) {\\n \\t\\t\\tconst result = this.lookForVacation(DayType.CP, calendarService);\\n\n \\t\\t\\tif (result.heuristic === SelectedDates.NO_VALID_STRATEGY_HEURISTIC) {\\n \\t\\t\\t\\tbreak;\\n\n \\t\\t\\t}\\n \\t\\t\\tvacationsNumber.cp -= 1;\\n+\\t\\t\\tprocessedDays++;\\n+\\t\\t\\tawait\n updateProgress();\\n \\t\\t}\\n \\t\\twhile (vacationsNumber.rtt > 0) {\\n \\t\\t\\tconst\n result = this.lookForVacation(DayType.RTT, calendarService);\\n \\t\\t\\tif (result.heuristic\n === SelectedDates.NO_VALID_STRATEGY_HEURISTIC) {\\n \\t\\t\\t\\tbreak;\\n \\t\\t\\t}\\n \\t\\t\\tvacationsNumber.rtt\n -= 1;\\n+\\t\\t\\tprocessedDays++;\\n+\\t\\t\\tawait updateProgress();\\n \\t\\t}\\n \\t\\twhile\n (vacationsNumber.other > 0) {\\n \\t\\t\\tconst result = this.lookForVacation(DayType.OTHER,\n calendarService);\\n \\t\\t\\tif (result.heuristic === SelectedDates.NO_VALID_STRATEGY_HEURISTIC)\n {\\n \\t\\t\\t\\tbreak;\\n \\t\\t\\t}\\n \\t\\t\\tvacationsNumber.other -= 1;\\n+\\t\\t\\tprocessedDays++;\\n+\\t\\t\\tawait\n updateProgress();\\n+\\t\\t}\\n+\\n+\\t\\t// Final progress update (100%)\\n+\\t\\tif (progressCallback)\n {\\n+\\t\\t\\tprogressCallback(100);\\n+\\t\\t}\\n+\\n+\\t\\t// Inform user if not all days\n were placed\\n+\\t\\tconst remainingDays = vacationsNumber.cp + vacationsNumber.rtt\n + vacationsNumber.other;\\n+\\t\\tif (remainingDays > 0) {\\n+\\t\\t\\tconsole.warn(\\n+\\t\\t\\t\\t`Optimization\n stopped: ${remainingDays} of ${totalDays} vacation days remain unplaced. ` +\\n+\\t\\t\\t\\t\\t`Too\n many days have equivalent placement quality - please place remaining days manually.`,\\n+\\t\\t\\t);\"\n:new_path: src/app/services/calendar-service.ts\n:old_path: src/app/services/calendar-service.ts\n:a_mode: '100644'\n:b_mode: '100644'\n:new_file: false\n", "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:16", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:17:15", "discussion_id": "1c8f21f72999404b53060774fb1e690285b8af5d", "id": 466, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "**Review:** Commented\n\n## Pull Request Overview\n\nThis PR enhances the vacation optimization feature by adding visual progress feedback during optimization and displaying warnings when vacation days cannot be placed automatically. The optimization algorithm has been made asynchronous to allow UI updates during processing.\n\n### Key Changes\n- Made vacation optimization asynchronous with progress callbacks\n- Added remaining vacation days tracking with a warning UI\n- Enhanced heuristic logic to stop optimization when too many days have equal placement quality\n\n### Reviewed Changes\n\nCopilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.\n\n
\nShow a summary per file\n\n| File | Description |\r\n| ---- | ----------- |\r\n| src/app/services/user-input-service.ts | Added signal to track remaining unplaced vacation days |\r\n| src/app/services/calendar-service.ts | Converted optimization to async, added progress tracking and stop condition for ambiguous placements |\r\n| src/app/components/user-input/user-input.ts | Added computed properties to display remaining vacation days warning |\r\n| src/app/components/user-input/user-input.html | Added warning card UI for unplaced vacation days |\r\n| src/app/components/user-input/user-input.scss | Added styles for warning card component |\r\n| src/app/components/calendar-year/calendar-year.ts | Integrated progress tracking with loading spinner |\r\n| src/app/components/calendar-year/calendar-year.html | Updated spinner to show progress percentage |\r\n| src/app/components/calendar-year/calendar-year.scss | Styled progress spinner with percentage overlay |\n
\n\n\n\n\n", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": false, "type": null, "updated_at": "2025-11-17T23:17:15", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "5ab29ef7700cb45e321469e2b5f69678a5761fff", "confidential": null, "created_at": "2025-11-17T23:17:34", "discussion_id": "9dfffc1a8aa9b415bdbc220d95dd91cd1066051a", "id": 272, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "039c4c160f861e3a0e271f37b5fff76a6ad3adcf_44_45", "namespace_id": null, "note": "`@copilot` open a new pull request to apply changes based on [this feedback](https://gitlab.nessar.fr/Nessar/vac-optimizer/-/merge_requests/49#discussion_r2535792120)", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": "---\n:diff: \"@@ -39,15 +40,21 @@ export class CalendarYear {\\n \\t\\t\\tconsole.log('Samedi\n Malin setting:', c);\\n \\t\\t\\tconsole.log('Optimizing vacations with data:', vacationData);\\n\n \\t\\t\\tthis.isLoading.set(true);\\n+\\t\\t\\tthis.loadingProgress.set(0);\\n \\t\\t\\t//\n Simulate server-side calculation with a small delay\\n-\\t\\t\\tsetTimeout(() => {\\n+\\t\\t\\tsetTimeout(async\n () => {\"\n:new_path: src/app/components/calendar-year/calendar-year.ts\n:old_path: src/app/components/calendar-year/calendar-year.ts\n:a_mode: '100644'\n:b_mode: '100644'\n:new_file: false\n", "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:17:34", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:17:41", "discussion_id": "8b5352e07c984620dd3bf079ccba4fb401ce73c8", "id": 465, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "`@THoguet` I've opened a new pull request, #50, to work on those changes. Once the pull request is ready, I'll request review from you.", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": false, "type": null, "updated_at": "2025-11-17T23:17:41", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:17:41", "discussion_id": "4af972d5943542400285efdbe536ff5d4562b39c", "id": 470, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "mentioned in merge request !50", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": true, "type": null, "updated_at": "2026-05-03T16:02:15.491127", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:18:12", "discussion_id": "f3ab5d0fcfc131027c5345971729820e6ab00483", "id": 467, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "requested review from `@Copilot`", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": true, "type": null, "updated_at": "2025-11-17T23:18:12", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "df1b8a509d2461e5086889d58dc8b58202166666", "confidential": null, "created_at": "2025-11-17T23:20:17", "discussion_id": "5e7a99d7d4d9264fe8c0277946d04234ef653223", "id": 278, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "c096123e634c24e4c6dfdac15c495e299686594a_300_300", "namespace_id": null, "note": "The magic number 9 for MAX_DAYS_WITH_SAME_HEURISTIC lacks explanation. Add a comment explaining why 9 is the threshold (e.g., based on testing, represents approximately one week of vacation days, or other reasoning).\n```suggestion:-0+0\n\tprivate static readonly NO_VALID_STRATEGY_HEURISTIC = -1;\n\t// MAX_DAYS_WITH_SAME_HEURISTIC is set to 9 to represent approximately one week of vacation days (5 workdays + 2 weekends) plus a buffer for common extended leave patterns, based on user testing and business requirements.\n```", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": "---\n:diff: \"@@ -298,6 +298,7 @@ export class SelectedDates implements SelectedDateInterface\n {\\n \\n \\t// Constant to indicate no valid strategy was found\\n \\tprivate static\n readonly NO_VALID_STRATEGY_HEURISTIC = -1;\"\n:new_path: src/app/services/calendar-service.ts\n:old_path: src/app/services/calendar-service.ts\n:a_mode: '100644'\n:b_mode: '100644'\n:new_file: false\n", "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:20:18", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "df1b8a509d2461e5086889d58dc8b58202166666", "confidential": null, "created_at": "2025-11-17T23:20:17", "discussion_id": "d2529a35a6dc1bcf6b3d0bcb571b6ba5f9278ffd", "id": 279, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "c096123e634c24e4c6dfdac15c495e299686594a_544_557", "namespace_id": null, "note": "Using `setTimeout(resolve, 0)` in a potentially tight loop during optimization could significantly slow down the process. For a vacation with 20+ days, this adds 20+ microtask delays. Consider throttling progress updates (e.g., only update every 5% or every N days) instead of on every iteration.\n```suggestion:-0+0\n\t\t// Helper function to update progress, throttled to every 5%\n\t\tlet lastReportedProgress = 0;\n\t\tconst PROGRESS_STEP = 5; // percent\n\t\tconst updateProgress = async () => {\n\t\t\tif (progressCallback && totalDays > 0) {\n\t\t\t\tconst progress = Math.round((processedDays / totalDays) * 100);\n\t\t\t\tif (progress - lastReportedProgress >= PROGRESS_STEP || progress === 100) {\n\t\t\t\t\tprogressCallback(progress);\n\t\t\t\t\tlastReportedProgress = progress;\n\t\t\t\t\t// Allow UI to update with a microtask\n\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 0));\n\t\t\t\t}\n```", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": "---\n:diff: \"@@ -535,33 +536,73 @@ export class SelectedDates implements SelectedDateInterface\n {\\n \\t\\treturn totalScore;\\n \\t}\\n \\n-\\toptimizeVacations(\\n+\\tasync optimizeVacations(\\n\n \\t\\tvacationsNumber: VacationsNumber,\\n \\t\\tcalendarSettingsService: CalendarSettingsService,\\n\n \\t\\tcalendarService: CalendarService,\\n-\\t): void {\\n+\\t\\tprogressCallback?: (progress:\n number) => void,\\n+\\t): Promise {\\n \\t\\tif (calendarSettingsService.get('samediMalin')())\n this.samediMalin(vacationsNumber);\\n+\\n+\\t\\tconst initialVacations = { ...vacationsNumber\n };\\n+\\t\\tconst totalDays = initialVacations.cp + initialVacations.rtt + initialVacations.other;\\n+\\t\\tlet\n processedDays = 0;\\n+\\n+\\t\\t// Helper function to update progress\\n+\\t\\tconst updateProgress\n = async () => {\\n+\\t\\t\\tif (progressCallback && totalDays > 0) {\\n+\\t\\t\\t\\tconst\n progress = Math.round((processedDays / totalDays) * 100);\\n+\\t\\t\\t\\tprogressCallback(progress);\\n+\\t\\t\\t\\t//\n Allow UI to update with a microtask\\n+\\t\\t\\t\\tawait new Promise((resolve) => setTimeout(resolve,\n 0));\"\n:new_path: src/app/services/calendar-service.ts\n:old_path: src/app/services/calendar-service.ts\n:a_mode: '100644'\n:b_mode: '100644'\n:new_file: false\n", "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:20:18", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "df1b8a509d2461e5086889d58dc8b58202166666", "confidential": null, "created_at": "2025-11-17T23:20:17", "discussion_id": "6e6edb5e5a6d69df10aff38df952870affded28f", "id": 280, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "3d779d5cb5b415447eb2a87265084f8874aabd0b_17_20", "namespace_id": null, "note": "The warning icon lacks an aria-label or other accessible text for screen readers. Add `aria-label=\\\"Warning\\\"` or use `aria-hidden=\\\"true\\\"` if the text description is sufficient.\n```suggestion:-0+0\n\t\t\t\twarning\n```", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": "---\n:diff: \"@@ -14,5 +14,16 @@\\n \\t\\t\\t\\n \\t\\t\\n \\t\\n+\\t@if (hasRemainingDays())\n {\\n+\\t\\t\\n+\\t\\t\\t\\n+\\t\\t\\t\\twarning\"\n:new_path: src/app/components/user-input/user-input.html\n:old_path: src/app/components/user-input/user-input.html\n:a_mode: '100644'\n:b_mode: '100644'\n:new_file: false\n", "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:20:18", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:20:18", "discussion_id": "458c95de59b025c2aebd63d4d0c62a2cdbc31c44", "id": 471, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "**Review:** Commented\n\n## Pull Request Overview\n\nCopilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.\n\n\n
\nComments suppressed due to low confidence (1)\n\n**src/app/components/user-input/user-input.ts:28**\n* The component uses constructor injection for `UserInputService`, but the coding guidelines specify using the `inject()` function instead. Consider changing to `private userInputService = inject(UserInputService);` at the class level for consistency with Angular best practices.\n```\n\tconstructor(private userInputService: UserInputService) {\n```\n
\n\n", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": false, "type": null, "updated_at": "2025-11-17T23:20:18", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:21:42", "discussion_id": "c07cbab837fdd606b507af0a647c40d706cfbe23", "id": 468, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "requested review from `@Copilot`", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": true, "type": null, "updated_at": "2025-11-17T23:21:42", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "5ab29ef7700cb45e321469e2b5f69678a5761fff", "confidential": null, "created_at": "2025-11-17T23:28:08", "discussion_id": "d13eff09204c438822ce9d9d36b07858ef3d1403", "id": 277, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "c096123e634c24e4c6dfdac15c495e299686594a_564_602", "namespace_id": null, "note": "`@copilot` open a new pull request to apply changes based on [this feedback](https://gitlab.nessar.fr/Nessar/vac-optimizer/-/merge_requests/49#r2535792156);\r\n\r\nalso remove constructor injection and use inject()", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": "---\n:diff: \"@@ -535,33 +536,73 @@ export class SelectedDates implements SelectedDateInterface\n {\\n \\t\\treturn totalScore;\\n \\t}\\n \\n-\\toptimizeVacations(\\n+\\tasync optimizeVacations(\\n\n \\t\\tvacationsNumber: VacationsNumber,\\n \\t\\tcalendarSettingsService: CalendarSettingsService,\\n\n \\t\\tcalendarService: CalendarService,\\n-\\t): void {\\n+\\t\\tprogressCallback?: (progress:\n number) => void,\\n+\\t): Promise {\\n \\t\\tif (calendarSettingsService.get('samediMalin')())\n this.samediMalin(vacationsNumber);\\n+\\n+\\t\\tconst initialVacations = { ...vacationsNumber\n };\\n+\\t\\tconst totalDays = initialVacations.cp + initialVacations.rtt + initialVacations.other;\\n+\\t\\tlet\n processedDays = 0;\\n+\\n+\\t\\t// Helper function to update progress\\n+\\t\\tconst updateProgress\n = async () => {\\n+\\t\\t\\tif (progressCallback && totalDays > 0) {\\n+\\t\\t\\t\\tconst\n progress = Math.round((processedDays / totalDays) * 100);\\n+\\t\\t\\t\\tprogressCallback(progress);\\n+\\t\\t\\t\\t//\n Allow UI to update with a microtask\\n+\\t\\t\\t\\tawait new Promise((resolve) => setTimeout(resolve,\n 0));\\n+\\t\\t\\t}\\n+\\t\\t};\\n+\\n+\\t\\tawait updateProgress();\\n+\\n \\t\\twhile (vacationsNumber.cp\n > 0) {\\n \\t\\t\\tconst result = this.lookForVacation(DayType.CP, calendarService);\\n\n \\t\\t\\tif (result.heuristic === SelectedDates.NO_VALID_STRATEGY_HEURISTIC) {\\n \\t\\t\\t\\tbreak;\\n\n \\t\\t\\t}\\n \\t\\t\\tvacationsNumber.cp -= 1;\\n+\\t\\t\\tprocessedDays++;\\n+\\t\\t\\tawait\n updateProgress();\\n \\t\\t}\\n \\t\\twhile (vacationsNumber.rtt > 0) {\\n \\t\\t\\tconst\n result = this.lookForVacation(DayType.RTT, calendarService);\\n \\t\\t\\tif (result.heuristic\n === SelectedDates.NO_VALID_STRATEGY_HEURISTIC) {\\n \\t\\t\\t\\tbreak;\\n \\t\\t\\t}\\n \\t\\t\\tvacationsNumber.rtt\n -= 1;\\n+\\t\\t\\tprocessedDays++;\\n+\\t\\t\\tawait updateProgress();\\n \\t\\t}\\n \\t\\twhile\n (vacationsNumber.other > 0) {\\n \\t\\t\\tconst result = this.lookForVacation(DayType.OTHER,\n calendarService);\\n \\t\\t\\tif (result.heuristic === SelectedDates.NO_VALID_STRATEGY_HEURISTIC)\n {\\n \\t\\t\\t\\tbreak;\\n \\t\\t\\t}\\n \\t\\t\\tvacationsNumber.other -= 1;\\n+\\t\\t\\tprocessedDays++;\\n+\\t\\t\\tawait\n updateProgress();\\n+\\t\\t}\\n+\\n+\\t\\t// Final progress update (100%)\\n+\\t\\tif (progressCallback)\n {\\n+\\t\\t\\tprogressCallback(100);\\n+\\t\\t}\\n+\\n+\\t\\t// Inform user if not all days\n were placed\\n+\\t\\tconst remainingDays = vacationsNumber.cp + vacationsNumber.rtt\n + vacationsNumber.other;\\n+\\t\\tif (remainingDays > 0) {\\n+\\t\\t\\tconsole.warn(\\n+\\t\\t\\t\\t`Optimization\n stopped: ${remainingDays} of ${totalDays} vacation days remain unplaced. ` +\\n+\\t\\t\\t\\t\\t`Too\n many days have equivalent placement quality - please place remaining days manually.`,\\n+\\t\\t\\t);\"\n:new_path: src/app/services/calendar-service.ts\n:old_path: src/app/services/calendar-service.ts\n:a_mode: '100644'\n:b_mode: '100644'\n:new_file: false\n", "system": false, "type": "LegacyDiffNote", "updated_at": "2025-11-17T23:29:57", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:30:05", "discussion_id": "b525b494651e334ed6aa24b907701bc229cce756", "id": 469, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "`@THoguet` I've opened a new pull request, #51, to work on those changes. Once the pull request is ready, I'll request review from you.", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": false, "type": null, "updated_at": "2025-11-17T23:30:05", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:30:05", "discussion_id": "e1ccb8c00f47780df1b02fa50f6cc9fc4c9ef49f", "id": 474, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "mentioned in merge request !51", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": true, "type": null, "updated_at": "2026-05-03T16:02:15.662895", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "54c2160f0dd293f8c1c78342e155a65d891041fd", "confidential": null, "created_at": "2025-11-17T23:35:59", "discussion_id": "8bc9f7406f79b1604b9c259d1aab3331b8fddc93", "id": 281, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "9109b4b9928db902ed369fb3cff4829b13128e5d_6_6", "namespace_id": null, "note": "The spinner should have an accessible label to inform screen reader users about the loading state and progress. The `mat-spinner` component should include `aria-label` or be wrapped in an element with an appropriate label.\n\nAdd an `aria-label` to the spinner:\n```html\n\n\n```\n```suggestion:-0+0\n\t\t\t\t\t\n\t\t\t\t\t\n```", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": "--- !ruby/object:Gitlab::Diff::Position\nattributes:\n :base_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :start_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :head_sha: 54c2160f0dd293f8c1c78342e155a65d891041fd\n :old_path: src/app/components/calendar-year/calendar-year.html\n :new_path: src/app/components/calendar-year/calendar-year.html\n :position_type: text\n :old_line:\n :new_line: 6\n :line_range:\n :ignore_whitespace_change: false\n", "position": "--- !ruby/object:Gitlab::Diff::Position\nattributes:\n :base_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :start_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :head_sha: 54c2160f0dd293f8c1c78342e155a65d891041fd\n :old_path: src/app/components/calendar-year/calendar-year.html\n :new_path: src/app/components/calendar-year/calendar-year.html\n :position_type: text\n :old_line:\n :new_line: 6\n :line_range:\n :ignore_whitespace_change: false\n", "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": false, "type": "DiffNote", "updated_at": "2025-11-17T23:36:00", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "54c2160f0dd293f8c1c78342e155a65d891041fd", "confidential": null, "created_at": "2025-11-17T23:35:59", "discussion_id": "72e1b08bd14eb0a3e1ad3cf8b31d9dcac81b0eb3", "id": 282, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "c096123e634c24e4c6dfdac15c495e299686594a_544_561", "namespace_id": null, "note": "Using `setTimeout(resolve, 0)` for every single day placement could cause performance issues when optimizing a large number of vacation days. Each `setTimeout` adds overhead and forces the event loop to cycle unnecessarily.\n\nConsider batching progress updates or using a minimum time interval between updates:\n```typescript\nconst updateProgress = async () => {\n\tif (progressCallback && totalDays > 0) {\n\t\tconst progress = Math.round((processedDays / totalDays) * 100);\n\t\t// Only update if progress changed by at least 1%\n\t\tif (progress !== this.lastProgress) {\n\t\t\tprogressCallback(progress);\n\t\t\tthis.lastProgress = progress;\n\t\t\t// Allow UI to update with a microtask\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 0));\n\t\t}\n\t}\n};\n```\n\nOr update progress only every N days:\n```typescript\nif (processedDays % 5 === 0 || processedDays === totalDays) {\n\tawait updateProgress();\n}\n```\n```suggestion:-7+0\n\t\tlet lastYieldTime = Date.now();\n\t\tconst YIELD_INTERVAL_MS = 100;\n\t\tconst updateProgress = async () => {\n\t\t\tif (progressCallback && totalDays > 0) {\n\t\t\t\tconst progress = Math.round((processedDays / totalDays) * 100);\n\t\t\t\tif (progress - lastReportedProgress >= PROGRESS_STEP || progress === 100) {\n\t\t\t\t\tprogressCallback(progress);\n\t\t\t\t\tlastReportedProgress = progress;\n\t\t\t\t\tconst now = Date.now();\n\t\t\t\t\tif (now - lastYieldTime >= YIELD_INTERVAL_MS) {\n\t\t\t\t\t\tlastYieldTime = now;\n\t\t\t\t\t\t// Allow UI to update with a microtask\n\t\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 0));\n\t\t\t\t\t}\n```", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": "--- !ruby/object:Gitlab::Diff::Position\nattributes:\n :base_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :start_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :head_sha: 54c2160f0dd293f8c1c78342e155a65d891041fd\n :old_path: src/app/services/calendar-service.ts\n :new_path: src/app/services/calendar-service.ts\n :position_type: text\n :old_line:\n :new_line: 561\n :line_range:\n :ignore_whitespace_change: false\n", "position": "--- !ruby/object:Gitlab::Diff::Position\nattributes:\n :base_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :start_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :head_sha: 54c2160f0dd293f8c1c78342e155a65d891041fd\n :old_path: src/app/services/calendar-service.ts\n :new_path: src/app/services/calendar-service.ts\n :position_type: text\n :old_line:\n :new_line: 561\n :line_range:\n :ignore_whitespace_change: false\n", "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": false, "type": "DiffNote", "updated_at": "2025-11-17T23:36:01", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "54c2160f0dd293f8c1c78342e155a65d891041fd", "confidential": null, "created_at": "2025-11-17T23:35:59", "discussion_id": "fc64e964e194bfe173bdc399115e50b58b4ff5ba", "id": 289, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "3d779d5cb5b415447eb2a87265084f8874aabd0b_17_18", "namespace_id": null, "note": "The warning message should have an `aria-live` attribute to announce changes to screen reader users. When the warning appears dynamically, assistive technologies should be notified.\n\nAdd `aria-live=\"polite\"` to the `mat-card` element:\n```html\n\n```\n```suggestion:-0+0\n\t\t\n```", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": "--- !ruby/object:Gitlab::Diff::Position\nattributes:\n :base_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :start_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :head_sha: 54c2160f0dd293f8c1c78342e155a65d891041fd\n :old_path: src/app/components/user-input/user-input.html\n :new_path: src/app/components/user-input/user-input.html\n :position_type: text\n :old_line:\n :new_line: 18\n :line_range:\n :ignore_whitespace_change: false\n", "position": "--- !ruby/object:Gitlab::Diff::Position\nattributes:\n :base_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :start_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :head_sha: 54c2160f0dd293f8c1c78342e155a65d891041fd\n :old_path: src/app/components/user-input/user-input.html\n :new_path: src/app/components/user-input/user-input.html\n :position_type: text\n :old_line:\n :new_line: 18\n :line_range:\n :ignore_whitespace_change: false\n", "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": false, "type": "DiffNote", "updated_at": "2025-11-17T23:36:01", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": "54c2160f0dd293f8c1c78342e155a65d891041fd", "confidential": null, "created_at": "2025-11-17T23:36:00", "discussion_id": "1a9b0e1c4f25560214390d8a636bf4034c998f66", "id": 291, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": "3d779d5cb5b415447eb2a87265084f8874aabd0b_17_23", "namespace_id": null, "note": "[nitpick] The warning icon should have an `aria-label` to provide context for screen readers. While `aria-hidden=\"true\"` prevents the icon from being announced, the warning text should be properly associated with the icon's semantic meaning.\n\nConsider wrapping the content in a semantic element with an appropriate role or adding an `aria-label` to the parent span:\n```html\n\n\twarning\n\t\n\t\t{{ remainingDays() }} jour{{ remainingDays() > 1 ? 's' : '' }} de vacances non\n\t\tplacé{{ remainingDays() > 1 ? 's' : '' }}. Veuillez les placer manuellement.\n\t\n\n```\n```suggestion:-3+0\n\t\t\t\t\n\t\t\t\t\twarning\n\t\t\t\t\t\n\t\t\t\t\t\t{{ remainingDays() }} jour{{ remainingDays() > 1 ? 's' : '' }} de vacances non\n\t\t\t\t\t\tplacé{{ remainingDays() > 1 ? 's' : '' }}. Veuillez les placer manuellement.\n\t\t\t\t\t\n```", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": "--- !ruby/object:Gitlab::Diff::Position\nattributes:\n :base_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :start_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :head_sha: 54c2160f0dd293f8c1c78342e155a65d891041fd\n :old_path: src/app/components/user-input/user-input.html\n :new_path: src/app/components/user-input/user-input.html\n :position_type: text\n :old_line:\n :new_line: 23\n :line_range:\n :ignore_whitespace_change: false\n", "position": "--- !ruby/object:Gitlab::Diff::Position\nattributes:\n :base_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :start_sha: 2d5cccff0ceac6890fb06c650e5aa078c1cf6b44\n :head_sha: 54c2160f0dd293f8c1c78342e155a65d891041fd\n :old_path: src/app/components/user-input/user-input.html\n :new_path: src/app/components/user-input/user-input.html\n :position_type: text\n :old_line:\n :new_line: 23\n :line_range:\n :ignore_whitespace_change: false\n", "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": false, "type": "DiffNote", "updated_at": "2025-11-17T23:36:01", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:36:00", "discussion_id": "c3548e1b972f4b88c5637d81fd6708ad7b38a402", "id": 472, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "**Review:** Commented\n\n## Pull Request Overview\n\nCopilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.\n\n\n\n", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": false, "type": null, "updated_at": "2025-11-17T23:36:00", "updated_by_id": null }, { "author_id": 3, "change_position": null, "commit_id": null, "confidential": null, "created_at": "2025-11-17T23:45:05", "discussion_id": "abfb43885063dbd6a02def51ab7f3264e4391538", "id": 473, "imported_from": 4, "internal": false, "last_edited_at": null, "line_code": null, "namespace_id": null, "note": "*Merged by: THoguet at 2025-11-17 23:45:05 UTC*", "noteable_id": 105, "noteable_type": "MergeRequest", "organization_id": null, "original_position": null, "position": null, "project_id": 35, "resolved_at": null, "resolved_by_id": null, "resolved_by_push": null, "review_id": null, "source_iid": 49, "source_item_project_id": 35, "st_diff": null, "system": false, "type": null, "updated_at": "2026-05-03T16:02:15.726133", "updated_by_id": null } ], "preexisting_missing_alternate_head": false, "source_assignees": [ { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" } ], "source_author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "source_created_at": "2025-11-17T23:14:45.000Z", "source_discussion_locked": null, "source_id": 105, "source_iid": 49, "source_item_metadata": { "assignee": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "assignees": [ { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" } ], "author": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "blocking_discussions_resolved": true, "closed_at": null, "closed_by": null, "created_at": "2025-11-17T23:14:45.000Z", "description": "…g for unplaced vacation days", "detailed_merge_status": "unchecked", "discussion_locked": null, "downvotes": 0, "draft": false, "force_remove_source_branch": null, "has_conflicts": false, "id": 105, "iid": 49, "imported": true, "imported_from": "github", "labels": [], "merge_after": null, "merge_commit_sha": null, "merge_status": "unchecked", "merge_user": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "merge_when_pipeline_succeeds": false, "merged_at": "2025-11-17T23:45:05.000Z", "merged_by": { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" }, "milestone": null, "prepared_at": null, "project_id": 35, "reference": "!49", "references": { "full": "nessar/vac-optimizer!49", "relative": "!49", "short": "!49" }, "reviewers": [ { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" } ], "sha": "54c2160f0dd293f8c1c78342e155a65d891041fd", "should_remove_source_branch": null, "source_branch": "THoguet/issue16", "source_project_id": 35, "squash": false, "squash_commit_sha": null, "squash_on_merge": false, "state": "merged", "target_branch": "main", "target_project_id": 35, "task_completion_status": { "completed_count": 0, "count": 0 }, "time_stats": { "human_time_estimate": null, "human_total_time_spent": null, "time_estimate": 0, "total_time_spent": 0 }, "title": "feat: enhance loading spinner with progress indication and add warnin…", "updated_at": "2026-05-03T16:01:05.881Z", "upvotes": 0, "user_notes_count": 22, "web_url": "https://gitlab.nessar.fr/nessar/vac-optimizer/-/merge_requests/49", "work_in_progress": false }, "source_kind": "MergeRequest", "source_path": "nessar/vac-optimizer", "source_project_id": 35, "source_reviewers": [ { "avatar_url": "https://secure.gravatar.com/avatar/5e51e115b51304582ee7b2cd8f249fe3ee71b46510e381f74c032e6de81d2757?s=80&d=identicon", "id": 3, "locked": false, "name": "admin", "public_email": null, "state": "active", "username": "admin-backup", "web_url": "https://gitlab.nessar.fr/admin-backup" } ], "source_updated_at": "2026-05-03T16:01:05.881Z", "state_events": [ { "close_after_error_tracking_resolve": false, "close_auto_resolve_prometheus_alert": false, "created_at": "2025-11-17T23:45:05+00:00", "epic_id": null, "id": 233, "imported_from": 4, "issue_id": null, "merge_request_id": 105, "namespace_id": 38, "source_commit": null, "source_merge_request_id": null, "state": 2, "user_id": 3 }, { "close_after_error_tracking_resolve": false, "close_auto_resolve_prometheus_alert": false, "created_at": "2025-11-17T23:45:05+00:00", "epic_id": null, "id": 234, "imported_from": 4, "issue_id": null, "merge_request_id": 105, "namespace_id": 38, "source_commit": "3272622abfbe608161aa51332e21b1cb23e3af93", "source_merge_request_id": null, "state": 3, "user_id": 3 } ], "target_number": 127 }