Skip to content

Comments

feat: Add option in room to always use sytem-wide default presentation#2746

Open
samuelwei wants to merge 7 commits intodevelopfrom
2662-add-option-to-always-include-default-slide
Open

feat: Add option in room to always use sytem-wide default presentation#2746
samuelwei wants to merge 7 commits intodevelopfrom
2662-add-option-to-always-include-default-slide

Conversation

@samuelwei
Copy link
Collaborator

@samuelwei samuelwei commented Jan 20, 2026

Fixes #2662

Type

  • Bugfix
  • Feature
  • Documentation
  • Refactoring (e.g. Style updates, Test implementation, etc.)
  • Other (please describe):

Checklist

  • Code updated to current develop branch head
  • Passes CI checks
  • Is a part of an issue
  • Tests added for the bugfix or newly implemented feature, describe below why if not
  • Changelog is updated
  • Documentation of code and features exists

Changes

  • Added option in room file management to always use system-wide default presentation
  • Added option in room file management to set system-wide default presentation as default

Other information

Summary by CodeRabbit

  • New Features

    • System-wide default presentation: admins can enable it for meetings, mark it as a room default, and manage it via new UI and API action.
  • UI/UX Improvements

    • "Edit" → "Configure" wording; file list shows meeting-availability tags, download allowed/not-allowed tags, per-file default indicators, and a system-default panel.
  • Bug Fixes

    • Tighter default handling to avoid unintended resets and ensure default-related flags persist immediately.
  • Chores

    • Database adds two flags to control system-default behavior; changelog updated.
  • Style

    • Minor animation and UI markup adjustments.

@samuelwei samuelwei linked an issue Jan 20, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds system-default presentation support: DB migration and Room flags, new FormRequest and controller method + route to manage system-default settings, Room and MeetingService changes to honor flags, frontend components/UI for configuring system-default, translations, and minor CSS/markup tweaks.

Changes

Cohort / File(s) Summary
Controller & API
app/Http/Controllers/api/v1/RoomFileController.php, app/Http/Requests/UpdateRoomSystemDefaultPresentation.php, routes/api.php
Add updateSystemDefault() and UpdateRoomSystemDefaultPresentation FormRequest; expose system_default in index payload; refine boolean/default handling in update(); add PUT route rooms/{room}/files/system_default.
Database & Model
database/migrations/...add_use_system_default_presentation_in_next_meeting_to_rooms_table.php, app/Models/Room.php
Add two boolean room columns and casts (use_system_default_presentation_in_meeting, use_system_default_presentation_as_default); updateDefaultFile() can early-return when system-default is configured as default.
Business Logic
app/Services/MeetingService.php
Inject system-default presentation into meeting payload when room flags require it; adjust ordering and duplication guards for default presentation.
Frontend — Files UI
resources/js/components/RoomTabFiles.vue, resources/js/components/RoomTabFilesConfigureButton.vue, resources/js/components/RoomTabFilesSystemDefault.vue
Add RoomTabFilesSystemDefault component and integrate into files tab; replace edit flow with configure flow; show default badges and system-default controls; load system_default from API.
Frontend — Minor UI & Styles
resources/js/components/InlineNote.vue, resources/css/override/_animations.css
Add inline-note class to Message and unconditional zero-duration animation override for .inline-note transitions.
Localization
lang/en/rooms.php
Rename/add keys for file configuration and system-default messaging; replace download visibility keys and change meeting-availability keys.
Changelog
CHANGELOG.md
Document new system-default presentation options and reference issue links.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement, frontend, UI, refactor

Suggested reviewers

  • danielmachill
  • Sabr1n4W
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title mentions 'system-wide default presentation' but contains a typo: 'sytem' instead of 'system'; it's unclear if this affects clarity. Fix the typo from 'sytem-wide' to 'system-wide' to ensure the title is clear and professional.
Out of Scope Changes check ❓ Inconclusive While most changes align with issue #2662, the CSS animation changes in _animations.css appear tangential and may indicate scope creep not directly tied to the presentation default feature.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description follows the template structure, provides issue reference (#2662), specifies feature type, marks relevant checklist items, lists changes, and indicates all key development steps completed.
Linked Issues check ✅ Passed The PR implements both core requirements from #2662: adding an option to always include the system-wide default presentation and to set it as default, aligning with the issue's solution request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2662-add-option-to-always-include-default-slide

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

❤️ Share

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

@codecov
Copy link

codecov bot commented Jan 20, 2026

Codecov Report

❌ Patch coverage is 45.65217% with 50 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.39%. Comparing base (967d685) to head (6541e56).

Files with missing lines Patch % Lines
...ources/js/components/RoomTabFilesSystemDefault.vue 30.95% 29 Missing ⚠️
app/Http/Controllers/api/v1/RoomFileController.php 53.84% 12 Missing ⚠️
...p/Requests/UpdateRoomSystemDefaultPresentation.php 0.00% 5 Missing ⚠️
app/Models/Room.php 50.00% 3 Missing ⚠️
app/Services/MeetingService.php 80.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             develop    #2746      +/-   ##
=============================================
- Coverage      96.77%   96.39%   -0.38%     
- Complexity      1839     1849      +10     
=============================================
  Files            444      446       +2     
  Lines          12570    12650      +80     
  Branches        2063     2076      +13     
=============================================
+ Hits           12164    12194      +30     
- Misses           406      456      +50     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
app/Models/Room.php (1)

196-214: System-default-as-default can be ignored due to early return.

If a room file is already default and use_in_meeting is true, the function returns before honoring use_system_default_presentation_as_default. This prevents the system default from becoming default when the option is enabled.

🔧 Proposed fix
-        $currentDefault = $this->files()->firstWhere('default', true);
+        $systemDefault = app(BigBlueButtonSettings::class)->default_presentation;
+        $currentDefault = $this->files()->firstWhere('default', true);
         if ($currentDefault != null) {
             // If the default file is also used in the next meeting, it can stay the default, otherwise remove default
             // and look for alternative
-            if ($currentDefault->use_in_meeting == true) {
+            if ($currentDefault->use_in_meeting == true && ! ($systemDefault && $this->use_system_default_presentation_as_default)) {
                 return;
             }
             $currentDefault->default = false;
             $currentDefault->save();
         }
 
         // If system has a default presentation and the system presentation is set as default, no further action needed
-        if (app(BigBlueButtonSettings::class)->default_presentation && $this->use_system_default_presentation_as_default) {
+        if ($systemDefault && $this->use_system_default_presentation_as_default) {
             return;
         }
🤖 Fix all issues with AI agents
In `@app/Http/Requests/UpdateRoomSystemDefaultPresentation.php`:
- Around line 7-15: The FormRequest subclass UpdateRoomSystemDefaultPresentation
is missing an authorize() override (FormRequest defaults authorize() to false),
so the endpoint will always 403; add an authorize() method to this class that
either returns true or implements the proper permission check (e.g., inspect the
current user/room ownership/ability) and keep the existing rules() validation
for 'use_in_meeting' and 'default' booleans; ensure the method name is authorize
and it returns a boolean reflecting the intended access control.

In `@resources/css/override/_animations.css`:
- Around line 12-15: The leave selector currently uses a global class
`.p-message-leave-active` which affects all message components; narrow its scope
by changing the selector to target only inline notes (e.g., use
`.inline-note.p-message-leave-active` alongside
`.inline-note.p-message-enter-active`) so the zero-duration animation applies
only to the `.inline-note` component and not globally.

In `@resources/js/components/RoomTabFiles.vue`:
- Around line 370-374: The API response may omit system_default for users
without viewAllFiles, so ensure systemDefault (ref) always has a safe object
before the template reads its properties: when assigning the API result to
systemDefault.value (in the RoomTabFiles.vue component where systemDefault is
updated), check if response.system_default is present and if not set
systemDefault.value to the existing default object { file: null, use_in_meeting:
false, use_as_default: false }; replace any direct assignment like
systemDefault.value = response.system_default with a guarded assignment that
falls back to the default ref value.

In `@resources/js/components/RoomTabFilesSystemDefault.vue`:
- Around line 52-60: The Button rendered as an anchor with target="_blank" in
RoomTabFilesSystemDefault.vue (the Button element using as="a", :href="file",
and data-test="room-files-view-button") needs rel="noopener noreferrer" to
prevent reverse-tabnabbing; update the Button props to include rel="noopener
noreferrer" so the generated <a> tag includes that attribute whenever
target="_blank" is used.

Comment on lines 52 to 60
<Button
v-tooltip="$t('rooms.files.view')"
:aria-label="$t('rooms.files.view')"
target="_blank"
icon="fa-solid fa-eye"
data-test="room-files-view-button"
as="a"
:href="file"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add rel="noopener noreferrer" on the view link.
target="_blank" without rel enables reverse‑tabnabbing.

🔒 Proposed fix
           v-tooltip="$t('rooms.files.view')"
           :aria-label="$t('rooms.files.view')"
           target="_blank"
+          rel="noopener noreferrer"
           icon="fa-solid fa-eye"
           data-test="room-files-view-button"
           as="a"
           :href="file"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
v-tooltip="$t('rooms.files.view')"
:aria-label="$t('rooms.files.view')"
target="_blank"
icon="fa-solid fa-eye"
data-test="room-files-view-button"
as="a"
:href="file"
/>
<Button
v-tooltip="$t('rooms.files.view')"
:aria-label="$t('rooms.files.view')"
target="_blank"
rel="noopener noreferrer"
icon="fa-solid fa-eye"
data-test="room-files-view-button"
as="a"
:href="file"
/>
🤖 Prompt for AI Agents
In `@resources/js/components/RoomTabFilesSystemDefault.vue` around lines 52 - 60,
The Button rendered as an anchor with target="_blank" in
RoomTabFilesSystemDefault.vue (the Button element using as="a", :href="file",
and data-test="room-files-view-button") needs rel="noopener noreferrer" to
prevent reverse-tabnabbing; update the Button props to include rel="noopener
noreferrer" so the generated <a> tag includes that attribute whenever
target="_blank" is used.

@cypress
Copy link

cypress bot commented Jan 20, 2026

PILOS    Run #2829

Run Properties:  status check passed Passed #2829  •  git commit 6541e5627e: feat: Add option in room to always use sytem-wide default presentation
Project PILOS
Branch Review 2662-add-option-to-always-include-default-slide
Run status status check passed Passed #2829
Run duration 07m 23s
Commit git commit 6541e5627e: feat: Add option in room to always use sytem-wide default presentation
Committer Samuel Weirich
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 613
View all changes introduced in this branch ↗︎

@samuelwei
Copy link
Collaborator Author

image

@samuelwei
Copy link
Collaborator Author

Feedback from @Sabr1n4W:

  • UI: System-wide default not part of the list, move above
  • UI: Default presentation badge, in the same line as filename

@Sabr1n4W
Copy link
Contributor

Feedback from @Sabr1n4W:

* UI: System-wide default not part of the list, move above

* UI: Default presentation badge, in the same line as filename

Another thing we discussed adressed the default behaviour when uploading a new presentation and making it available in the next meeting for the first time (no other presentation enabled).

Current default behaviour

  • System-wide default presentation will not be available for the next meeting and is not the default presentation anymore after adding a new presentation and making it available
  • Newly added and marked as available presentation will be the default presentation for the next meeting

Possible adjusted default behaviour

  • Keep the system-wide default presentation available for the next meeting and keep it as the default presentation after adding a new presentation and making it available
    • If behaviour was not actively changed by the user (Through the settings for the system-wide default presentation)
  • Do not automatically mark the newly added and marked as available presentation as the default presentation
    • User can still mark the presentation as the default if required when making the presentation available

Reason for suggested changes of the default behaviour

  • The user has to mark a presentation as available before it will be available in the next meeting. Inside of the dialog which is used to mark the presentation as available there is another option to mark the presentation as the default presentation.
  • Because of that the fact that presentation is automatically marked as the default presentation if no other presentation is available even though the user decided against this setting could be confusing.
  • The default behaviour should be as easy to understand as possible and should try to prevent unexpected behaviour like marking a presentation as default presentation even though the user decided against it when marking the presentation as available (did not mark as default)

Possible disadvantages

  • Suggested behaviour regarding the system-wide default presentation is very different to the current behaviour before this PR
    • Could feel unusual for users that already know the current behaviour (before this PR)
    • Would be necessary to find a solution how to handle the migration

Additional possible changes

  • Adding a visual feedback which settings will be automatically applied when marking a presentation as available could also help to prevent confusion and unexpected behaviours
    • e.g. visual feedback inside the dialog when marking a presentation as available if the presentation will automatically be marked as the default presentation (show correct value in the dialog where the presentation is marked as available)
    • e.g. feedback / info that the system-wide default presentation will not be available anymore if the current presentation is marked as available

@samuelwei samuelwei force-pushed the 2662-add-option-to-always-include-default-slide branch from f54187d to 802d541 Compare February 18, 2026 09:07
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
app/Http/Controllers/api/v1/RoomFileController.php (2)

140-141: Missing PHPDoc comment on updateSystemDefault().

Every other public method in this controller has a docblock; updateSystemDefault has none.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Http/Controllers/api/v1/RoomFileController.php` around lines 140 - 141,
Add a PHPDoc docblock above the public function
updateSystemDefault(UpdateRoomSystemDefaultPresentation $request, Room $room)
describing the method's purpose, the parameter types and names (`@param`
UpdateRoomSystemDefaultPresentation $request, `@param` Room $room), the return
type (`@return` \Illuminate\Http\JsonResponse or whatever other response is used
by other methods), and any relevant authorization or behavior notes to match the
style of other public methods in this controller; place the docblock directly
above the updateSystemDefault declaration so it matches existing method
docblocks.

120-128: No database transaction wrapping related multi-step writes.

Both update() (lines 120–128) and updateSystemDefault() (lines 140–165) perform a direct bulk DB update ($room->files()->update(...)) followed by $room->save(). If $room->save() fails, file default flags are already cleared in the database while the room's own flags remain unchanged, leaving data in an inconsistent state.

♻️ Suggested refactor (example for `updateSystemDefault`)
+        \Illuminate\Support\Facades\DB::transaction(function () use ($request, $room) {
         if ($request->has('use_in_meeting')) {
             $room->use_system_default_presentation_in_meeting = $request->use_in_meeting;
         }

         if ($request->has('default')) {
             if ($request->default === true) {
                 $room->files()->update(['default' => false]);
                 $room->use_system_default_presentation_in_meeting = true;
             }
             $room->use_system_default_presentation_as_default = $request->default;
         }

         $room->save();
+        });

Also applies to: 140-165

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Http/Controllers/api/v1/RoomFileController.php` around lines 120 - 128,
The multi-step updates in RoomFileController (methods update and
updateSystemDefault) must be wrapped in a DB transaction to avoid partial
commits: move the bulk file update calls (e.g. $room->files()->whereNot('id',
$file->id)->update(...) and $room->files()->update(...)) and the subsequent
$room->save() into a single DB::transaction(...) block (or manual
beginTransaction/commit/rollback) so that if $room->save() fails the prior
updates are rolled back; ensure exceptions are caught and rethrown or handled so
the transaction rolls back on error and preserve existing behavior (including
throwing/returning validation errors).
app/Services/MeetingService.php (1)

107-133: Cache BigBlueButtonSettings instance – app() is called 4 times.

♻️ Suggested refactor
+        $bbbSettings = app(BigBlueButtonSettings::class);
+
         // Use system default presentation as default, if explicitly set
-        $useSystemDefaultFileAsDefault = app(BigBlueButtonSettings::class)->default_presentation && $this->meeting->room->use_system_default_presentation_as_default;
+        $useSystemDefaultFileAsDefault = $bbbSettings->default_presentation && $this->meeting->room->use_system_default_presentation_as_default;
         if ($useSystemDefaultFileAsDefault) {
-            $meetingParams->addPresentation(app(BigBlueButtonSettings::class)->default_presentation);
+            $meetingParams->addPresentation($bbbSettings->default_presentation);
         }

         // ...

-        if (! $useSystemDefaultFileAsDefault && app(BigBlueButtonSettings::class)->default_presentation && (
-            empty($files->toArray()) || $this->meeting->room->use_system_default_presentation_in_meeting
+        if (! $useSystemDefaultFileAsDefault && $bbbSettings->default_presentation && (
+            empty($files->toArray()) || $this->meeting->room->use_system_default_presentation_in_meeting
         )) {
-            $meetingParams->addPresentation(app(BigBlueButtonSettings::class)->default_presentation);
+            $meetingParams->addPresentation($bbbSettings->default_presentation);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Services/MeetingService.php` around lines 107 - 133, The code calls
app(BigBlueButtonSettings::class) multiple times; cache that instance into a
local variable (e.g. $bbbSettings) at the start of the method in MeetingService
and replace all occurrences (the calls within the $useSystemDefaultFileAsDefault
assignment, the two meetingParams->addPresentation calls, and the conditional
that checks default_presentation and use_system_default_presentation_in_meeting)
with that cached $bbbSettings->default_presentation / $bbbSettings->... to avoid
repeated container lookups and improve readability.
app/Http/Requests/UpdateRoomSystemDefaultPresentation.php (1)

9-15: required validation makes the controller's $request->has() guards dead code.

Both use_in_meeting and default are required, so validation rejects the request if either is absent — meaning $request->has(...) is always true inside updateSystemDefault(). The partial-update pattern in the controller implies sometimes, but the rules enforce presence.

Either change the rules to ['sometimes', 'boolean'] to allow genuine partial updates, or remove the has() guards in RoomFileController::updateSystemDefault() to match the current always-present contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Http/Requests/UpdateRoomSystemDefaultPresentation.php` around lines 9 -
15, The validation in UpdateRoomSystemDefaultPresentation::rules currently marks
'use_in_meeting' and 'default' as required which makes the $request->has(...)
checks in RoomFileController::updateSystemDefault() redundant; to preserve the
controller's partial-update behavior, change the rules in
UpdateRoomSystemDefaultPresentation::rules to use ['sometimes','boolean'] for
both 'use_in_meeting' and 'default' so missing fields are allowed and still
validated when present.
app/Models/Room.php (1)

208-214: Stale/incomplete comment on line 213.

The comment // If no default file is explicitly set or the system default should be used, ends with a dangling comma and doesn't match what follows (unconditionally picks the first use_in_meeting file). The addition at lines 208–211 is logically correct.

✏️ Suggested comment fix
-        // If no default file is explicitly set or the system default should be used,
+        // No explicit default set; promote the first in-meeting file to default
         $newDefaultFile = $this->files()->firstWhere('use_in_meeting', true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Models/Room.php` around lines 208 - 214, The inline comment above the
selection of a new default file is incomplete and misleading; update the comment
to accurately describe the unconditional behavior of choosing the first file
with use_in_meeting set to true when the system default presentation is not
being used. Replace the dangling comment (the one before the $newDefaultFile
assignment) with a clear line such as: "If we are not using the system default
presentation, pick the first file marked use_in_meeting as the new default."
Reference BigBlueButtonSettings, the property
use_system_default_presentation_as_default, and the
files()->firstWhere('use_in_meeting', true) call to make intent explicit.
lang/en/rooms.php (1)

141-143: New keys placed out of alphabetical order.

always_available_in_meeting, available_in_next_meeting, and not_available_in_next_meeting (all starting with a/n) are appended after uploaded instead of being inserted at their alphabetical positions within the files array. This makes the file harder to scan for existing keys.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lang/en/rooms.php` around lines 141 - 143, The three translation keys
'always_available_in_meeting', 'available_in_next_meeting', and
'not_available_in_next_meeting' were appended after 'uploaded' instead of being
placed alphabetically inside the files array; move those three entries into the
files array at their correct alphabetical positions (so they appear where other
keys starting with "a" and "n" belong), preserving their exact values and
surrounding formatting/commas.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lang/en/rooms.php`:
- Line 141: Rename the translation key files.always_available_in_meeting to
files.always_available_in_next_meeting and update its value text to remove the
ambiguity — either make it one-shot like siblings (e.g., "Available in next
video conference") or persistent (e.g., "Always available in video
conferences"); pick the correct intent, change the key to
always_available_in_next_meeting if it's next-meeting scoped, and update any
component references that use always_available_in_meeting to the new key name.
- Around line 112-115: The English translation keys for file actions were
changed but not propagated—update all locale files that still define the old
keys (files.download_hidden, files.download_visible, files.edit,
files.use_in_next_meeting, files.use_in_next_meeting_disabled) to instead define
the new keys (download_allowed, download_not_allowed,
always_available_in_meeting OR rename to always_available_in_next_meeting for
consistency, available_in_next_meeting, not_available_in_next_meeting,
configure) so all locales match English; update all E2E assertions in
RoomsViewFiles.cy.js and RoomsViewFilesFileActions.cy.js to expect the new keys
(download_allowed, download_not_allowed, always_available_in_next_meeting or
always_available_in_next_meeting if you rename, available_in_next_meeting,
not_available_in_next_meeting, configure); if you choose to rename
always_available_in_meeting to always_available_in_next_meeting, rename that key
across translations, code references and tests to keep naming consistent.

In `@resources/js/components/RoomTabFilesSystemDefault.vue`:
- Around line 92-99: Static analysis flags that the Button attribute order
should place the static attribute autofocus before the event binding `@click`;
update the Button element in RoomTabFilesSystemDefault.vue (the Button that uses
:label="$t('app.cancel')", severity="secondary", :disabled="isLoadingAction",
data-test="dialog-cancel-button", `@click`="modalVisible = false", autofocus) so
that the autofocus attribute appears before the `@click` attribute (i.e., move
autofocus to precede `@click`) while preserving all other props and bindings.
- Around line 155-182: Add a missing disabled prop to the component's
defineProps (e.g., add disabled: { type: Boolean, default: false }) so the
parent's :disabled="isBusy" is accepted, and update the View and Configure
buttons to use a combined disabled state (use props.disabled || isLoadingAction)
so they are inert while the parent is busy or an internal action is loading;
modify the button bindings in RoomTabFilesSystemDefault.vue where the "View" and
"Configure" buttons are rendered and reference props.disabled and
isLoadingAction accordingly.

---

Duplicate comments:
In `@resources/js/components/RoomTabFiles.vue`:
- Around line 429-436: The component crashes because systemDefault.value is set
directly from response.data.system_default which can be undefined; update the
fetch handler in the API promise (.then) so systemDefault.value is assigned a
safe default (e.g., null or an empty object) using a null-coalescing fallback
(assign systemDefault.value = response.data.system_default ?? null) and ensure
any downstream code or template uses safe access (e.g., guards like
systemDefault && systemDefault.file) — locate the assignment to
systemDefault.value in the block that also sets files.value, defaultFile.value
and calls paginator.updateMeta and apply this defensive assignment there.

In `@resources/js/components/RoomTabFilesSystemDefault.vue`:
- Around line 52-60: The anchor rendered by the Button with target="_blank" (the
<Button ... as="a" target="_blank" :href="file" ... /> instance) is missing a
rel attribute which allows reverse-tabnabbing; update that Button invocation to
include rel="noopener noreferrer" so the generated <a> tag uses rel="noopener
noreferrer" (ensure the change is applied to the Button component usage that has
as="a", target="_blank", and :href="file").

---

Nitpick comments:
In `@app/Http/Controllers/api/v1/RoomFileController.php`:
- Around line 140-141: Add a PHPDoc docblock above the public function
updateSystemDefault(UpdateRoomSystemDefaultPresentation $request, Room $room)
describing the method's purpose, the parameter types and names (`@param`
UpdateRoomSystemDefaultPresentation $request, `@param` Room $room), the return
type (`@return` \Illuminate\Http\JsonResponse or whatever other response is used
by other methods), and any relevant authorization or behavior notes to match the
style of other public methods in this controller; place the docblock directly
above the updateSystemDefault declaration so it matches existing method
docblocks.
- Around line 120-128: The multi-step updates in RoomFileController (methods
update and updateSystemDefault) must be wrapped in a DB transaction to avoid
partial commits: move the bulk file update calls (e.g.
$room->files()->whereNot('id', $file->id)->update(...) and
$room->files()->update(...)) and the subsequent $room->save() into a single
DB::transaction(...) block (or manual beginTransaction/commit/rollback) so that
if $room->save() fails the prior updates are rolled back; ensure exceptions are
caught and rethrown or handled so the transaction rolls back on error and
preserve existing behavior (including throwing/returning validation errors).

In `@app/Http/Requests/UpdateRoomSystemDefaultPresentation.php`:
- Around line 9-15: The validation in UpdateRoomSystemDefaultPresentation::rules
currently marks 'use_in_meeting' and 'default' as required which makes the
$request->has(...) checks in RoomFileController::updateSystemDefault()
redundant; to preserve the controller's partial-update behavior, change the
rules in UpdateRoomSystemDefaultPresentation::rules to use
['sometimes','boolean'] for both 'use_in_meeting' and 'default' so missing
fields are allowed and still validated when present.

In `@app/Models/Room.php`:
- Around line 208-214: The inline comment above the selection of a new default
file is incomplete and misleading; update the comment to accurately describe the
unconditional behavior of choosing the first file with use_in_meeting set to
true when the system default presentation is not being used. Replace the
dangling comment (the one before the $newDefaultFile assignment) with a clear
line such as: "If we are not using the system default presentation, pick the
first file marked use_in_meeting as the new default." Reference
BigBlueButtonSettings, the property use_system_default_presentation_as_default,
and the files()->firstWhere('use_in_meeting', true) call to make intent
explicit.

In `@app/Services/MeetingService.php`:
- Around line 107-133: The code calls app(BigBlueButtonSettings::class) multiple
times; cache that instance into a local variable (e.g. $bbbSettings) at the
start of the method in MeetingService and replace all occurrences (the calls
within the $useSystemDefaultFileAsDefault assignment, the two
meetingParams->addPresentation calls, and the conditional that checks
default_presentation and use_system_default_presentation_in_meeting) with that
cached $bbbSettings->default_presentation / $bbbSettings->... to avoid repeated
container lookups and improve readability.

In `@lang/en/rooms.php`:
- Around line 141-143: The three translation keys 'always_available_in_meeting',
'available_in_next_meeting', and 'not_available_in_next_meeting' were appended
after 'uploaded' instead of being placed alphabetically inside the files array;
move those three entries into the files array at their correct alphabetical
positions (so they appear where other keys starting with "a" and "n" belong),
preserving their exact values and surrounding formatting/commas.

Comment on lines +112 to +115
'download_allowed' => 'Download allowed',
'download_not_allowed' => 'Download not allowed',
'downloadable' => 'Downloadable',
'edit' => 'Edit file',
'configure' => 'Configure file',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for dangling references to removed translation keys
rg -n --type=vue --type=js --type=php \
  -e "files\.download_hidden" \
  -e "files\.download_visible" \
  -e "files\.edit['\"]" \
  -e "use_in_next_meeting" \
  -e "download_hidden" \
  -e "download_visible" \
  .

Repository: THM-Health/PILOS

Length of output: 87


🏁 Script executed:

#!/bin/bash
# Also search in JSON files and any files that might reference these keys
rg -n "download_hidden|download_visible|use_in_next_meeting" \
  --type=json --type=ts --type=jsx .

Repository: THM-Health/PILOS

Length of output: 87


🏁 Script executed:

#!/bin/bash
# Check for other locale files that may need updates
fd -e php -path "*/lang/*" | head -20

Repository: THM-Health/PILOS

Length of output: 228


🏁 Script executed:

#!/bin/bash
# Read the actual file to understand the context of changes
cat -n lang/en/rooms.php | sed -n '100,150p'

Repository: THM-Health/PILOS

Length of output: 2927


🏁 Script executed:

#!/bin/bash
# Search for dangling references to removed keys with simpler patterns
echo "=== Checking for references to removed keys ==="
rg "download_hidden|download_visible" -n --no-heading
rg "files\.edit" -n --no-heading
rg "use_in_next_meeting" -n --no-heading

Repository: THM-Health/PILOS

Length of output: 7710


🏁 Script executed:

#!/bin/bash
# Find all locale files
echo "=== Finding all locale translation files ==="
find . -path "*/lang/*/rooms.php" -type f

Repository: THM-Health/PILOS

Length of output: 231


🏁 Script executed:

#!/bin/bash
# Check for files that reference the old keys
rg "download_hidden" . 2>/dev/null | head -20

Repository: THM-Health/PILOS

Length of output: 1063


🏁 Script executed:

#!/bin/bash
# Look for Vue component files that might reference these translation keys
find . -name "*.vue" -o -name "*.js" | head -20 | xargs rg -l "download_hidden|download_visible|use_in_next_meeting" 2>/dev/null || echo "No matches found"

Repository: THM-Health/PILOS

Length of output: 76


Critical: Translation keys were changed in English but not propagated to other locales or E2E tests.

The removed keys (files.download_hidden, files.download_visible, files.edit, files.use_in_next_meeting, files.use_in_next_meeting_disabled) are still referenced in:

  • E2E tests: ~50+ assertions across RoomsViewFiles.cy.js and RoomsViewFilesFileActions.cy.js expect these old keys to appear in the UI
  • Other language files: de, de-gender, fr, fa, ar all still define the old keys while the English file uses new ones

This creates a critical inconsistency where E2E tests will fail and non-English locales will have mismatched translation keys. All language files must be updated together with the same keys, and E2E tests must be updated to check for the new keys (download_allowed, download_not_allowed, always_available_in_meeting, available_in_next_meeting, not_available_in_next_meeting, configure).

Additionally, line 141 has a key naming inconsistency: always_available_in_meeting omits the _next_ infix present in available_in_next_meeting and not_available_in_next_meeting. Consider renaming to always_available_in_next_meeting for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lang/en/rooms.php` around lines 112 - 115, The English translation keys for
file actions were changed but not propagated—update all locale files that still
define the old keys (files.download_hidden, files.download_visible, files.edit,
files.use_in_next_meeting, files.use_in_next_meeting_disabled) to instead define
the new keys (download_allowed, download_not_allowed,
always_available_in_meeting OR rename to always_available_in_next_meeting for
consistency, available_in_next_meeting, not_available_in_next_meeting,
configure) so all locales match English; update all E2E assertions in
RoomsViewFiles.cy.js and RoomsViewFilesFileActions.cy.js to expect the new keys
(download_allowed, download_not_allowed, always_available_in_next_meeting or
always_available_in_next_meeting if you rename, available_in_next_meeting,
not_available_in_next_meeting, configure); if you choose to rename
always_available_in_meeting to always_available_in_next_meeting, rename that key
across translations, code references and tests to keep naming consistent.

'uploaded' => 'File \':name\' uploaded',
'use_in_next_meeting' => 'Use in the next meeting',
'use_in_next_meeting_disabled' => 'Not available in video conference',
'always_available_in_meeting' => 'Always available in next video conference',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Key name is inconsistent with its siblings and the value text is ambiguous.

  • The key always_available_in_meeting drops the _next_ infix, while both siblings use _in_next_meeting. For consistency it should be always_available_in_next_meeting.
  • The value "Always available in next video conference" is internally contradictory: always implies all future meetings, while next implies only the immediately upcoming one. If the intent is a sticky/persistent setting, consider "Always available in video conferences"; if it is one-shot like the others, drop "always".
✏️ Proposed rename and wording fix
-        'always_available_in_meeting' => 'Always available in next video conference',
+        'always_available_in_next_meeting' => 'Always available in video conferences',

Any component referencing files.always_available_in_meeting would need to be updated to files.always_available_in_next_meeting accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lang/en/rooms.php` at line 141, Rename the translation key
files.always_available_in_meeting to files.always_available_in_next_meeting and
update its value text to remove the ambiguity — either make it one-shot like
siblings (e.g., "Available in next video conference") or persistent (e.g.,
"Always available in video conferences"); pick the correct intent, change the
key to always_available_in_next_meeting if it's next-meeting scoped, and update
any component references that use always_available_in_meeting to the new key
name.

Comment on lines +92 to +99
<Button
:label="$t('app.cancel')"
severity="secondary"
:disabled="isLoadingAction"
data-test="dialog-cancel-button"
@click="modalVisible = false"
autofocus
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

autofocus must precede @click (static analysis warning on line 98).

✏️ Proposed fix
         <Button
           :label="$t('app.cancel')"
           severity="secondary"
           :disabled="isLoadingAction"
           data-test="dialog-cancel-button"
+          autofocus
           `@click`="modalVisible = false"
-          autofocus
         />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
:label="$t('app.cancel')"
severity="secondary"
:disabled="isLoadingAction"
data-test="dialog-cancel-button"
@click="modalVisible = false"
autofocus
/>
<Button
:label="$t('app.cancel')"
severity="secondary"
:disabled="isLoadingAction"
data-test="dialog-cancel-button"
autofocus
`@click`="modalVisible = false"
/>
🧰 Tools
🪛 GitHub Check: Frontend Code Style Check

[warning] 98-98:
Attribute "autofocus" should go before "@click"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/js/components/RoomTabFilesSystemDefault.vue` around lines 92 - 99,
Static analysis flags that the Button attribute order should place the static
attribute autofocus before the event binding `@click`; update the Button element
in RoomTabFilesSystemDefault.vue (the Button that uses
:label="$t('app.cancel')", severity="secondary", :disabled="isLoadingAction",
data-test="dialog-cancel-button", `@click`="modalVisible = false", autofocus) so
that the autofocus attribute appears before the `@click` attribute (i.e., move
autofocus to precede `@click`) while preserving all other props and bindings.

Comment on lines +155 to +182
<script setup>
import env from "../env";
import { useApi } from "../composables/useApi.js";
import { ref, watch } from "vue";
import { useFormErrors } from "../composables/useFormErrors.js";

const props = defineProps({
file: {
type: String,
default: null,
},
roomId: {
type: String,
required: true,
},
useInMeeting: {
type: Boolean,
default: false,
},
useAsDefault: {
type: Boolean,
default: false,
},
defaultFile: {
type: Object,
default: null,
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing disabled prop — parent's isBusy signal is silently dropped.

RoomTabFiles.vue passes :disabled="isBusy" to this component (line 56 of that file), but disabled is not declared in defineProps. In Vue 3 with <script setup>, it falls through as an HTML attribute on the root <div> and never reaches the View or Configure buttons. Both buttons remain interactive while the parent is reloading data, which can cause the modal to open with stale props.

✏️ Proposed fix
 const props = defineProps({
   file: { type: String, default: null },
   roomId: { type: String, required: true },
   useInMeeting: { type: Boolean, default: false },
   useAsDefault: { type: Boolean, default: false },
   defaultFile: { type: Object, default: null },
+  disabled: { type: Boolean, default: false },
 });

Then use :disabled="props.disabled || isLoadingAction" on the View and Configure buttons.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/js/components/RoomTabFilesSystemDefault.vue` around lines 155 -
182, Add a missing disabled prop to the component's defineProps (e.g., add
disabled: { type: Boolean, default: false }) so the parent's :disabled="isBusy"
is accepted, and update the View and Configure buttons to use a combined
disabled state (use props.disabled || isLoadingAction) so they are inert while
the parent is busy or an internal action is loading; modify the button bindings
in RoomTabFilesSystemDefault.vue where the "View" and "Configure" buttons are
rendered and reference props.disabled and isLoadingAction accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
app/Models/Room.php (1)

207-223: ⚠️ Potential issue | 🔴 Critical

updateDefaultFile() can silently revert an explicit user choice to not use the system default as the room default.

Consider this scenario:

  1. A room has use_system_default_presentation_in_meeting = true and no files with use_in_meeting = true.
  2. The user explicitly sets use_system_default_presentation_as_default = false via updateSystemDefault() (with default=false).
  3. The controller saves the room, then calls updateDefaultFile().
  4. Inside updateDefaultFile(), no file has default = true, so $currentDefault is null and execution falls through to line 210.
  5. The condition default_presentation && use_system_default_presentation_in_meeting is true, so the method sets use_system_default_presentation_as_default = trueoverriding the user's explicit opt-out.

The root cause is that updateDefaultFile() doesn't distinguish between "no default was ever set" and "the user actively chose not to have the system default." You likely need to guard against overwriting an explicit false set in the same request, or restructure so that updateDefaultFile() only promotes the system default when no explicit user decision was made.

One way to fix this: don't call updateDefaultFile() at all when the user explicitly sets default=false in the updateSystemDefault endpoint, or add a parameter/flag so updateDefaultFile knows to respect the explicit choice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Models/Room.php` around lines 207 - 223, updateDefaultFile() currently
promotes the system default when default_presentation &&
use_system_default_presentation_in_meeting without respecting an explicit user
opt-out; change the flow so updateDefaultFile() does not override an explicit
choice made by updateSystemDefault(): either (A) have the controller that
handles updateSystemDefault() skip calling updateDefaultFile() when the payload
contains default=false, or (B) add a parameter/flag to
updateDefaultFile($respectExplicitChoice = true) and, when true, have
updateDefaultFile() check for an explicit user decision (e.g. detect if
use_system_default_presentation_as_default was deliberately set to false in the
current request or if a dedicated flag was passed) before setting
use_system_default_presentation_as_default = true; ensure references to
updateDefaultFile(), updateSystemDefault(),
use_system_default_presentation_as_default,
use_system_default_presentation_in_meeting, and default_presentation are updated
accordingly.
🧹 Nitpick comments (2)
database/migrations/2026_01_19_105554_add_use_system_default_presentation_in_next_meeting_to_rooms_table.php (1)

34-37: Minor: combine dropColumn calls into a single array.

Proposed fix
         Schema::table('rooms', function (Blueprint $table) {
-            $table->dropColumn('use_system_default_presentation_in_meeting');
-            $table->dropColumn('use_system_default_presentation_as_default');
+            $table->dropColumn([
+                'use_system_default_presentation_in_meeting',
+                'use_system_default_presentation_as_default',
+            ]);
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@database/migrations/2026_01_19_105554_add_use_system_default_presentation_in_next_meeting_to_rooms_table.php`
around lines 34 - 37, In the migration's down/alter block where
Schema::table(...) uses a Blueprint $table, replace the two separate
$table->dropColumn('use_system_default_presentation_in_meeting') and
$table->dropColumn('use_system_default_presentation_as_default') calls with a
single $table->dropColumn([...]) call that passes both column names as an array;
update the Schema::table callback (the function (Blueprint $table) block) to
call dropColumn once with the array of the two column names.
resources/js/components/RoomTabFilesSystemDefault.vue (1)

28-44: <p> used as a flex layout container is semantically incorrect.

A <p> element is phrasing content and shouldn't serve as a flex column wrapper. While PrimeVue's Tag renders a <span> (so it's technically not invalid), the intent here is clearly layout, not a paragraph.

♻️ Proposed fix
-          <p class="m-0 flex flex-col gap-2 text-sm">
+          <div class="m-0 flex flex-col gap-2 text-sm">
             <Tag
               v-if="useInMeeting"
               severity="success"
               :value="$t('rooms.files.always_available_in_meeting')"
             />
             <Tag
               v-else-if="defaultFile === null"
               severity="success"
               :value="$t('rooms.files.available_in_next_meeting')"
             />
             <Tag
               v-else
               severity="secondary"
               :value="$t('rooms.files.not_available_in_next_meeting')"
             />
-          </p>
+          </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/js/components/RoomTabFilesSystemDefault.vue` around lines 28 - 44,
Replace the semantic misuse of the <p> element used as a flex container in
RoomTabFilesSystemDefault.vue: change the paragraph wrapper that contains the
Tag components (the element with classes "m-0 flex flex-col gap-2 text-sm") to a
neutral block element such as a <div> (or <section>) so layout is preserved but
semantics are correct; keep the existing classes and the Tag usage
(useInMeeting, defaultFile checks) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/Http/Controllers/api/v1/RoomFileController.php`:
- Around line 144-169: The updateSystemDefault method currently reads booleans
directly from $request and can leave an inconsistent state when use_in_meeting
is set to false without default being provided; change all raw accesses
($request->use_in_meeting and $request->default) to use
$request->boolean('use_in_meeting') and $request->boolean('default') for
consistent boolean coercion, and when you detect use_in_meeting is explicitly
false while the request did not include an explicit default=false, also clear
use_system_default_presentation_as_default (set it to false) so the room cannot
have a default presentation that is not included in the meeting; keep the
existing behavior in the block where default === true (files()->update(...) and
setting use_system_default_presentation_in_meeting = true) and then save and
call updateDefaultFile() as before.

In
`@database/migrations/2026_01_19_105554_add_use_system_default_presentation_in_next_meeting_to_rooms_table.php`:
- Around line 20-25: Replace the per-model loop that loads Room models via
RoomFile::each (the RoomFile model and its $file->room access) with a single raw
DB update to avoid N+1 and model observer side-effects: compute the set of room
IDs that have a default, use_in_meeting RoomFile and then run a
DB::table('rooms')->whereIn('id',
$ids)->update(['use_system_default_presentation_as_default' => false]); also add
use Illuminate\Support\Facades\DB; to the file header; do not call Room::save()
or rely on Room observers in the migration.

---

Outside diff comments:
In `@app/Models/Room.php`:
- Around line 207-223: updateDefaultFile() currently promotes the system default
when default_presentation && use_system_default_presentation_in_meeting without
respecting an explicit user opt-out; change the flow so updateDefaultFile() does
not override an explicit choice made by updateSystemDefault(): either (A) have
the controller that handles updateSystemDefault() skip calling
updateDefaultFile() when the payload contains default=false, or (B) add a
parameter/flag to updateDefaultFile($respectExplicitChoice = true) and, when
true, have updateDefaultFile() check for an explicit user decision (e.g. detect
if use_system_default_presentation_as_default was deliberately set to false in
the current request or if a dedicated flag was passed) before setting
use_system_default_presentation_as_default = true; ensure references to
updateDefaultFile(), updateSystemDefault(),
use_system_default_presentation_as_default,
use_system_default_presentation_in_meeting, and default_presentation are updated
accordingly.

---

Duplicate comments:
In `@resources/js/components/RoomTabFilesSystemDefault.vue`:
- Around line 90-97: In RoomTabFilesSystemDefault.vue update the Button element
so the static attribute autofocus appears before the event binding `@click`;
specifically, in the Button with :label="$t('app.cancel')" and
:disabled="isLoadingAction" move the autofocus token to precede
`@click`="modalVisible = false" (keeping all other props intact) to satisfy the
analyzer warning.
- Around line 159-180: The component is missing a disabled prop so parent
:disabled="isBusy" doesn't reach the buttons; add a disabled Boolean prop to the
defineProps (default false) and then apply it to the View and Configure buttons
by setting their disabled binding to combine the prop with the component's
internal loading state (e.g., :disabled="props.disabled || isLoadingAction") so
the buttons become non-interactive when the parent or the component is busy.
- Around line 51-59: The Button anchor in RoomTabFilesSystemDefault.vue
currently uses target="_blank" (the <Button ... as="a" target=\"_blank\"
:href=\"file\" ... /> instance) which is vulnerable to reverse-tabnabbing;
update that Button to include rel="noopener noreferrer" (i.e., add rel="noopener
noreferrer" to the Button props when as="a" and target="_blank") so the external
link opened in a new tab cannot access the opener.

---

Nitpick comments:
In
`@database/migrations/2026_01_19_105554_add_use_system_default_presentation_in_next_meeting_to_rooms_table.php`:
- Around line 34-37: In the migration's down/alter block where
Schema::table(...) uses a Blueprint $table, replace the two separate
$table->dropColumn('use_system_default_presentation_in_meeting') and
$table->dropColumn('use_system_default_presentation_as_default') calls with a
single $table->dropColumn([...]) call that passes both column names as an array;
update the Schema::table callback (the function (Blueprint $table) block) to
call dropColumn once with the array of the two column names.

In `@resources/js/components/RoomTabFilesSystemDefault.vue`:
- Around line 28-44: Replace the semantic misuse of the <p> element used as a
flex container in RoomTabFilesSystemDefault.vue: change the paragraph wrapper
that contains the Tag components (the element with classes "m-0 flex flex-col
gap-2 text-sm") to a neutral block element such as a <div> (or <section>) so
layout is preserved but semantics are correct; keep the existing classes and the
Tag usage (useInMeeting, defaultFile checks) unchanged.

Comment on lines +144 to +169
public function updateSystemDefault(UpdateRoomSystemDefaultPresentation $request, Room $room)
{
if ($request->has('use_in_meeting')) {
$room->use_system_default_presentation_in_meeting = $request->use_in_meeting;
}

if ($request->has('default')) {
// Make other files not the default
if ($request->default === true) {
$room->files()->update(['default' => false]);

// If system default is set as default, it must also be used in the next meeting
$room->use_system_default_presentation_in_meeting = true;
}

$room->use_system_default_presentation_as_default = $request->default;
}

$room->save();

Log::info('Changed system default presentation settings in room {room}', ['room' => $room->getLogLabel()]);

$room->updateDefaultFile();

return response()->noContent();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Two issues in updateSystemDefault().

1. Inconsistent boolean access — Lines 147 and 159 use $request->use_in_meeting and $request->default (raw property access), while update() at lines 113/117 uses $request->boolean(...). Raw access can pass through strings like "1" or "0" rather than actual booleans. Use $request->boolean() for consistency.

2. Setting use_in_meeting = false doesn't clear as_default — If the user disables the system presentation for the meeting (use_in_meeting = false) without explicitly sending default = false, the room ends up with use_system_default_presentation_as_default = true while use_system_default_presentation_in_meeting = false. This is an inconsistent state — the presentation can't be the default if it's not included in the meeting.

Proposed fix
     public function updateSystemDefault(UpdateRoomSystemDefaultPresentation $request, Room $room)
     {
         if ($request->has('use_in_meeting')) {
-            $room->use_system_default_presentation_in_meeting = $request->use_in_meeting;
+            $room->use_system_default_presentation_in_meeting = $request->boolean('use_in_meeting');
+
+            // If system default is removed from the meeting, it cannot remain the default
+            if (!$request->boolean('use_in_meeting')) {
+                $room->use_system_default_presentation_as_default = false;
+            }
         }
 
         if ($request->has('default')) {
             // Make other files not the default
-            if ($request->default === true) {
+            if ($request->boolean('default')) {
                 $room->files()->update(['default' => false]);
 
                 // If system default is set as default, it must also be used in the next meeting
                 $room->use_system_default_presentation_in_meeting = true;
             }
 
-            $room->use_system_default_presentation_as_default = $request->default;
+            $room->use_system_default_presentation_as_default = $request->boolean('default');
         }
 
         $room->save();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Http/Controllers/api/v1/RoomFileController.php` around lines 144 - 169,
The updateSystemDefault method currently reads booleans directly from $request
and can leave an inconsistent state when use_in_meeting is set to false without
default being provided; change all raw accesses ($request->use_in_meeting and
$request->default) to use $request->boolean('use_in_meeting') and
$request->boolean('default') for consistent boolean coercion, and when you
detect use_in_meeting is explicitly false while the request did not include an
explicit default=false, also clear use_system_default_presentation_as_default
(set it to false) so the room cannot have a default presentation that is not
included in the meeting; keep the existing behavior in the block where default
=== true (files()->update(...) and setting
use_system_default_presentation_in_meeting = true) and then save and call
updateDefaultFile() as before.

Comment on lines +20 to +25
RoomFile::where('default', true)
->where('use_in_meeting', true)
->each(function (RoomFile $file) {
$file->room->use_system_default_presentation_as_default = false;
$file->room->save();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Data migration triggers N+1 queries and risks model/observer side effects.

Two concerns with using Eloquent models here:

  1. N+1 queries: Each iteration loads $file->room individually. For a large number of rooms with default files, this could be slow. A single query would be more efficient.

  2. Model coupling: If Room's observer (RoomObserver) performs side-effects on save (e.g., logging, cache invalidation), those will fire for every room during migration. Migrations should ideally be decoupled from application-layer concerns.

A raw DB update avoids both issues:

Proposed fix
-        RoomFile::where('default', true)
-            ->where('use_in_meeting', true)
-            ->each(function (RoomFile $file) {
-                $file->room->use_system_default_presentation_as_default = false;
-                $file->room->save();
-            });
+        DB::table('rooms')
+            ->whereIn('id', function ($query) {
+                $query->select('room_id')
+                    ->from('room_files')
+                    ->where('default', true)
+                    ->where('use_in_meeting', true);
+            })
+            ->update(['use_system_default_presentation_as_default' => false]);

You'll need to add use Illuminate\Support\Facades\DB; at the top.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RoomFile::where('default', true)
->where('use_in_meeting', true)
->each(function (RoomFile $file) {
$file->room->use_system_default_presentation_as_default = false;
$file->room->save();
});
DB::table('rooms')
->whereIn('id', function ($query) {
$query->select('room_id')
->from('room_files')
->where('default', true)
->where('use_in_meeting', true);
})
->update(['use_system_default_presentation_as_default' => false]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@database/migrations/2026_01_19_105554_add_use_system_default_presentation_in_next_meeting_to_rooms_table.php`
around lines 20 - 25, Replace the per-model loop that loads Room models via
RoomFile::each (the RoomFile model and its $file->room access) with a single raw
DB update to avoid N+1 and model observer side-effects: compute the set of room
IDs that have a default, use_in_meeting RoomFile and then run a
DB::table('rooms')->whereIn('id',
$ids)->update(['use_system_default_presentation_as_default' => false]); also add
use Illuminate\Support\Facades\DB; to the file header; do not call Room::save()
or rely on Room observers in the migration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add option to always include default slide

2 participants