> ## Documentation Index
> Fetch the complete documentation index at: https://theteam-13fc7f49.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Issues & Errors

> Known bugs and open issues across backend and frontend, grouped by priority — mirror of the known-issues table in LMS_Frontend/major_code_updates.md.

The live known-issues table lives at the top of `LMS_Frontend/major_code_updates.md`
(**last reviewed: 07/10/2026 05 PM**). This page groups those rows — plus the open "TO BE FIXED"
items from the update entries — **by priority**. The `#N` markers are the row numbers in that
table, so the two stay easy to cross-reference.

This page was re-checked on **09/10/2026** against the 08/10/2026 clean-up (dead code deleted,
every package bumped) and Updates 90–94, and on **19/10/2026** for Update 123 (rows #34 and #35 added,
the chapter-quiz backfill closed) and Update 124 (row #36 added), and on **20/10/2026** for Update 129 (rows #40–#42 added; one more row is tracked privately in the frontend log), and on **01/11/2026** for Update 184 (rows #50 and #51, the R2 bucket folders, closed: see [Recently resolved](#recently-resolved-for-context)) and Update 185 (row #5 narrowed, row #55 added), and on **04/11/2026** for Updates 223–224 (row #7 narrowed, row #63 added; one more row is tracked privately in the frontend log). Notes marked *09/10* record what changed since the table
was last reviewed; the table itself has not been updated yet.

| Priority | Meaning |
| :- | :- |
| 🔴 **Critical** | Security / data-exposure holes reachable today — fix first |
| 🚩 **High** | Wrong behavior a user can hit, or a cross-tenant / data-integrity defect |
| ⚠️ **Medium** | Real gaps and perf costs with workarounds in place |
| 🔵 **Low** | Cosmetic issues, noise and technical debt |

***

## 🔴 Critical — security & data exposure

> [accordion] #1 — POST /certification trusts the client (Backend)
>
> The endpoint checks neither that `student_id` is the caller nor that the training / exam was
> completed (`CertificationPolicy::create` returns `true` for everyone). The "finished" gate
> exists **only in the Flutter UI** — a logged-in user can issue a certification for an
> unfinished course, or for another user, by calling the API directly.
> **Fix:** server-side ownership + completion check in `CertificationsController::store`.

> [accordion] #17 — POST /notifications/send has no role check (Backend)
>
> `SendUnifiedNotificationRequest::authorize()` returns `true` and the controller checks no role:
> **any signed-in user, a student included**, can notify any user, role, or the whole company.
> **Fix:** allow admins / instructors only, and limit an instructor to the learners of their own
> classrooms.

> [accordion] #21 — Classroom write endpoints have no role check (Backend)
>
> `POST /classroom/create`, `PUT /classroom/update/{id}`, archive / unarchive and
> `DELETE /classroom/{id}/force_delete` check no role (a student creating a classroom answers
> `201`). `update` and `force_delete` also skip the company filter, so **another company's
> classroom can be changed or deleted**. Update 73 only fixed the *read* endpoints.
> **Fix:** staff only (instructor limited to own classrooms) + `where('company_id', …)`.

> [accordion] Full-payload endpoints still open to any student (Backend — Update 44 follow-up)
>
> `GET /training/{id}` (`TrainingPolicy::view` is `return true`) still returns **every lesson's
> content and every quiz answer key**, and `GET /quiz/{id}` is reachable because students hold
> `QUIZZES_VIEW` by default. The marketplace no longer calls them (Update 44), but anyone who
> types the URL still gets the data. **Fix:** make both staff / owner-only now that no student
> screen needs them.

> [accordion] ⚠️ Unverified — GET /quiz/\{id} may ship the answer key before an exam starts
>
> Flagged by the knowledge-graph refresh (Update 89, source `authed_user_and_attempts.md`) and
> **not yet re-checked against the code**. Trace the endpoint through the providers and screens;
> if confirmed, promote it into the table above.

***

## 🚩 High — wrong behavior users can hit

> [accordion] #7 — companyId: 1 hardcoded in student flows (Frontend)
>
> `StudentProvider.fetchPaginatedTrainings`, the `createCertificateApi` call sites, their
> notification refresh and `QuizProvider.uploadFile()` all send `company_id: 1` — **wrong for any
> tenant other than company 1**. **Fix:** read the company from the signed-in user (and grep for
> the remaining hardcoded instances — they're latent multi-tenant bugs).
> *04/11:* the notifications page now sends the signed-in user's `company_id` (Update 223); the
> other call sites above are unchanged.

> [accordion] #20 — Dashboard 'Edit profile information' opens the shopping cart (Frontend)
>
> `profile_side_widget.dart` calls `studentNavigationShell.goBranch(5)` — the **cart** branch,
> not Settings (6). **Fix:** navigate like the app-bar avatar does (`setSelectedIndex(6)` +
> `goBranch(6)` + the settings sub-branch).
> *09/10:* the file was **deleted** on 08/10/2026 by the dead-code clean-up (Section D — nothing
> imported it), so the button no longer exists anywhere in the app. The source table still
> carries the row; it can be closed.

> [accordion] #29 — Archived trainings stay visible to their owners (Frontend + Backend)
>
> `myTrainings` is built at login from `orders[].trainings`, whose relation deliberately drops
> the `archived` scope — so an archived training still shows on My Trainings, the dashboard cards
> and the overview counts (Update 82 only fixed the Certificates "In progress" list), and the two
> data sources disagree until a refetch. **Fix:** decide the product rule, then filter
> `archivedAt == null` where `myTrainings` is built — or scope the login resource.

> [accordion] #2 — Certificate created but the API answers 500 (Backend)
>
> The "congratulations" notification is inserted **outside the transaction**; if it throws, the
> app shows "Fail Creation Certificate" for a certificate that exists (a retry returns it with
> `already_exists: true`). **Fix:** guard the notification (try/catch or queue).

> [accordion] #22 — An archived classroom cannot be deleted (Backend)
>
> `DELETE /classroom/{id}/force_delete` hides archived classrooms in its lookup, so it answers
> `404` — yet the admin table only offers Delete **on** archived classrooms. **Fix:** look up
> with `withArchived()` (plus the #21 company filter).

> [accordion] #23 — Endless spinner when a classroom request fails (Frontend)
>
> When `GET /classroom/{id}` fails (403 for a non-member, 404, network),
> `getClassroomById` shows the error dialog and returns null — the page keeps its loading spinner
> forever behind it. **Fix:** an error state with a back button.

> [accordion] #31 — No backend 'all records' mode for the staff lists (Backend)
>
> The `Archivable` scope cannot return active + archived together, so the frontend builds the
> "All" view from two paginated lists: **2–3 requests per page** and active rows always sorted
> before archived ones. **Fix:** accept `archive_status=active|archived|all` and return
> `archived_at` in every row (see Suggested Changes).

***

## ⚠️ Medium — gaps and performance costs with workarounds

> [accordion] #3 — /preview-trainings has no total\_quizzes\_count (Backend)
>
> The catalog glass cards **hide the quizzes stat** rather than under-count it. Adding
> `total_quizzes_count` (chapter quizzes + final quiz) lights it up with no Flutter change.

> [accordion] #5 — My Content tab bodies are rebuilt with UniqueKey (Frontend)
>
> Every rebuild / tab switch recreates the five tab bodies: `CertificatesScreen` refetches
> `/certifications/by-user` and the My Trainings search field loses its text. **Fix:** stable
> keys (or none).
>
> *01/11/2026 (Update 185):* My Trainings and My Exam Simulator now have stable keys (their search,
> category filter and groups survive rebuilds). Only Certificates and Exam Attempts still use
> `UniqueKey()`.

> [accordion] #12 — Three HTML renderers still bypass AppHtmlContent (Frontend)
>
> The training preview's lesson dialog, question explanations
> (`question_explanation_view.dart`) and the admin add-training preview render HTML outside the
> shared renderer — a YouTube iframe inside a question explanation would show "Error 153" on
> mobile. `LessonContentScreen` is a fourth, unused copy. **Fix:** migrate them; delete
> `LessonContentScreen`.
> *09/10:* `LessonContentScreen` was deleted on 08/10/2026 (dead-code Section D); the three live
> renderers still need the migration.

> [accordion] #13 — Leaderboard score is all-time only, no levels (Backend)
>
> The card leaves out the template's "This week" pill and "Level – Tier" line because the API has
> neither a weekly window nor a level. Don't fake them client-side (see Suggested Changes).

> [accordion] #16 — Validated lessons carry no completion date (Backend)
>
> The stats card shows "Avg. N% complete" but cannot show "· 2 touched this week". **Fix:** add
> the pivot `created_at` to the user's lessons.

> [accordion] #24 — Blank band above the keyboard on the Marketplace search (Frontend, phones)
>
> With the keyboard open the page stops \~90px above it (bottom bar + system inset counted twice,
> cause not yet isolated). With a tall keyboard on a 320px display the search field is hidden
> while typing. **Fix:** find which scaffold applies the keyboard inset under the bottom bar and
> drop one of the two.

> [accordion] #30 — Arabic certificates get no social-share image (Backend)
>
> `CertificateImageService` draws with GD, which cannot shape right-to-left scripts — Arabic
> certificates fall back to the training badge in link previews. **Fix:** bundle an Arabic font
> and shape the text, or render that case with a headless browser.

> [accordion] Course player initial load & lesson-complete round-trip (Backend, Update 37)
>
> `GET /training/{id}` ships every lesson's full HTML up front (dominant initial-load cost), and
> "Mark as complete & continue" waits on a full `GET /user/{id}` refetch. Both fixes are specced
> in Suggested Changes.

> [accordion] Web cold-load: uncompressed canvaskit.wasm + no Cache-Control (Server, Update 33)
>
> The 7.2 MB CanvasKit engine ships raw and nothing is cacheable, so first paint takes seconds
> even on good internet. The nginx directives are written but **not yet applied** on the vhosts
> (the single biggest perf win available).

> [accordion] #19 — Dead search state in StudentClassroomProvider (Frontend)
>
> `filteredLearners` / `filterLearners` / `searchController` are unread since Update 69 (the
> members panel keeps its own search). No effect today; remove in a cleanup pass.

> [accordion] #34 — Chapter import skips the chapter's quizzes (Backend)
>
> `POST /chapters/import` creates the chapter and its lessons only: the quizzes of an exported
> chapter file (`quizzes`, or `quiz` in older files) are not imported, so exporting a chapter and
> importing it into another training loses its quizzes. **Fix:** import each entry of `quizzes[]`
> with the quiz import code of `Export_Import_Quizzes_Exams_Controller` (row added 19/10/2026, Update 123).

> [accordion] #35 — Chapter quizzes cannot be reordered or placed between lessons (Backend + Frontend)
>
> Since Update 123 a chapter holds several quizzes, shown after its lessons in creation order. There
> is no quiz position or reorder endpoint, and lessons and quizzes cannot be interleaved; the training
> editor lists the quizzes read-only. **Fix:** `quizzes.position` + `POST /quizzes/reorder`, then drag
> handles on the curriculum's quiz rows.

> [accordion] #36 — Validation errors answer 500 instead of 422 on most endpoints (Backend)
>
> 27 controllers run `validate()` inside `try { … } catch (\Throwable $th)`, which returns a `500` with
> the message instead of the `422` with per-field `errors`. The app then shows a server error for a bad
> input. Update 124 fixed `GET /trainings` and `GET /quizzes`. **Fix:** rethrow
> `ValidationException` before the `\Throwable` catch, or validate before the `try` (row added
> 19/10/2026, Update 124). *20/10:* `POST /classroom/create` and `PUT /classroom/update/{id}`
> fixed too (Update 131, the slug work).

> [accordion] #40 — Chatrooms: Laravel Reverb not running on the servers yet (Server)
>
> The chatroom code shipped in Update 129, but no server runs `php artisan reverb:start` and
> `BROADCAST_DRIVER` is still `log`, so the apps poll every 5 seconds instead of receiving messages
> instantly. **Fix:** Reverb keys + `BROADCAST_DRIVER=reverb`, a supervised `reverb:start`, and the
> nginx `/app/` proxy ([Chatrooms Setup Guide](https://theteam-13fc7f49.mintlify.site/deployment/chatrooms-setup.md), section 5).

> [accordion] #41 — Chatroom images: server upload limits below 20 MB (Server)
>
> The paperclip accepts images up to 20 MB, but PHP's default `upload_max_filesize` /
> `post_max_size` and nginx's `client_max_body_size` are much lower, so a large image fails.
> **Fix:** 25M (PHP) and `client_max_body_size 25m;` (nginx) in Plesk
> ([Chatrooms Setup Guide](https://theteam-13fc7f49.mintlify.site/deployment/chatrooms-setup.md), section 3).

> [accordion] #55 — Training JSON import builds broken slugs (Backend)
>
> **TO BE FIXED (backend), found 01/11/2026 (Update 185).** `POST /trainings/export-import/import`
> builds the slug from the raw label with a lower-case-only pattern *before* lowercasing it and cuts
> it to 13 characters, so "Imported training" becomes `-mported-trai`; a taken slug gets a random
> suffix instead of `-2`, `-3`. Imported trainings get broken URLs and R2 folders. **Fix:** use
> `Training::generateUniqueSlug($trainingData['slug'] ?? $trainingData['label'])`, like `POST /training`.

> [accordion] #63 — Konnect payment init sends the new phone format unchecked (Backend)
>
> **To check (backend), found 04/11/2026 (Update 224).** The app now saves `phone_number` as
> "+216 98378579" (country code, one space, digits). The Konnect init payload sends it as saved;
> Konnect's expected format was not checked and no payment was tried. If Konnect expects 8 local
> digits, its checkout may show the number wrong or refuse it. **Fix:** check against Konnect; if
> needed, send only the digits after "+216 " for a Tunisian number (nothing for a foreign one).

> [accordion] #42 — No scheduler cron documented on the servers (Server)
>
> `chatrooms:prune-attachments` (unsent chatroom images) and `notifications:delete-expired` only run
> with an every-minute `php artisan schedule:run` cron, which no deployment note sets up.
> **Fix:** add the cron in Plesk → Scheduled tasks
> ([Chatrooms Setup Guide](https://theteam-13fc7f49.mintlify.site/deployment/chatrooms-setup.md), section 3).

***

## 🔵 Low — cosmetic, noise and technical debt

> [accordion] #4 — test/widget\_test.dart is entirely commented out (Frontend)
>
> `flutter test` always ends with one "failed to load" error. **Fix:** delete the file or restore
> a real test. *09/10:* still the only failure — 436 tests pass otherwise (Update 94).

> [accordion] Filters sheet shared by the Exam Catalog and Instructors tabs (Frontend, Update 90 note)
>
> Both pages read `ExamProvider.showSideSheetExam` for their Filters side sheet, so a sheet opened
> on one tab is still open after switching to the other. Pre-existing, but visible now that the
> two are neighbouring Marketplace tabs. **Fix:** one flag per tab, or close the sheet on a tab
> change.

> [accordion] 'Platform access' reminder puts a password in a stored message (design caveat, Update 94)
>
> The new message suggestion sends a learner their login details, so the password lands in the
> `notifications` table, in the admin Recent log and in the learner's in-app notifications (and in
> any future BCC archive mailbox). Nothing to fix in code — use it knowingly, and replace the
> `xxxyyyzzz` placeholders before sending.

> [accordion] #8 — Exam card sets the My Content tab without navigating (Frontend)
>
> `exam_card_with_progress.dart` calls `updatePageIndex(2)` without going there; since Update 59
> the URL decides the tab, so the hint is overridden. Cosmetic. **Fix:** drop the call or use
> `goToMyContentTab`.

> [accordion] #9 — Analyzer debt: 116 issues (0 errors / 21 warnings / 95 infos)
>
> Full breakdown and fix order in `major_code_updates.md` Update 45. The notable buckets:
> **13 `use_build_context_synchronously`** (real "widget disposed" crash risk — the Konnect
> payment flow first), 38 `Radio` → `RadioGroup`, 5 `ShowCaseWidget` (removed in showcaseview
> v6 — migrate **before** upgrading), 7 `onReorder` → `onReorderItem` (⚠️ remove the manual index
> adjustment when migrating), 12 `Matrix4` calls, 12 unused imports, 6 experimental
> `flutter_quill` APIs. 28 of the 116 are `dart fix`-able.
> *09/10:* after the 08/10/2026 package bump and dead-code batch, `flutter analyze` reports
> **0 errors / 10 warnings / 134 infos**: the 75 deprecations and 13
> `use_build_context_synchronously` are unchanged, and `flutter_lints` 6 added 53
> `strict_top_level_inference` infos (members without a declared type, mostly in providers such
> as `chapter_provider.dart`). The `ShowCaseWidget` / `Radio` / `onReorder` buckets above still
> apply.

> [accordion] #10 — Laravel Pint fails on 4 files (Backend)
>
> `CertificationsController.php` and the three `resources/lang/*/messages.php` carry pre-existing
> formatting findings. No runtime effect. **Fix:** a formatting-only commit.

> [accordion] #15 — Latin name + period renders mirrored in Arabic (Frontend)
>
> "Sami Z." inside RTL text draws as ".Sami Z" — standard bidi behavior. **Fix:** give name texts
> their own text direction (or a bidi isolate).

> [accordion] #25 — 'View certifica…' cut on 360px phones (Frontend)
>
> The compact completed-training card's gold button truncates on Galaxy-S-width / zoomed
> displays. **Fix:** shorter label or smaller font in the compact card.

> [accordion] #26 — Forced-width pages scroll sideways on 11" windows (Frontend)
>
> Help Center and learner analytics are laid out at 1235px+, the exam-taking pages at 1458px,
> inside sideways scroll views. **Fix:** size from the real width, as My Content does since
> Update 76 (Help Center is shared with the staff roles).

> [accordion] #27 — Button labels missing the Poppins family (Frontend)
>
> `FilledButton.styleFrom(textStyle: …)` without `fontFamily` replaces the theme style — \~12
> styles in 5 files would render in the browser's fallback font. **Fix:** add
> `fontFamily: 'Poppins'`.

***

## ⚠️ Verification debt (#11)

Updates 53 to 69 (and most later UI work) are **verified by widget tests only** — the glass cards'
real scrolling cost, the certificate flows, the tab URLs, the remade leaderboard / continue-learning
/ progress cards, the classroom notify dialog and more have **not been looked at in a real browser
or on a device**, and the real `POST /certification` round trip runs through a fake client.
Updates 90 to 94 (the Marketplace Instructors tab, the avatar-menu language row, the instructor
pager arrows, the re-colored admin reminders page) and the 08/10/2026 package bump were likewise
verified through tests and the test renderer only — and the bump still lacks an iOS build and a
manual pass on the screens whose packages changed (drag-and-drop questions, uploads, YouTube
fullscreen on web, 2FA recovery-codes download, the three dropdowns, certificate download on
Android).

A manual pass on web and on a phone is the standing action item: visual or timing problems would
not show in the tests. Many individual update entries end with "Not checked in a real browser" for
exactly this reason.

***

## ✅ Recently resolved (for context)

* **#50** R2 uploads landing flat in `files/` whatever folder the app named, and **#51** the three
  backend-only folders outside the layout (`chatrooms/{slug}/`, `files/conversations/`,
  `files/instructor_background_items/`) → closed on 01/11/2026 (backend `R2StoragePathService` +
  Update 184): the backend builds every key from the ids the app sends (`training_id`, `lesson_id`,
  `classroom_id`, `quiz_id`), one folder per training / classroom / quiz / chatroom slug under
  `files/`; a folder string from a client is never trusted; older objects keep their keys. See
  [R2 Storage Paths](https://theteam-13fc7f49.mintlify.site/api-reference/r2-storage-paths.md).

* **Login payload bugs B1 – B4, B6, B7 of the [Auth User Optimize](https://theteam-13fc7f49.mintlify.site/clean-up/auth-user-optimize.md) plan**
  → closed by Update 125 (19/10/2026): progress and attempt ceilings come from the backend, the
  Attempts tab loads its rows on open, certificates carry `training_id` / `quiz_id` at boot.

* **#33** stale page-title sizes in `student_web_responsive_test.dart` → the expectation was aligned
  with the code in Update 125.

* **`GET /auth/user` payload size** (1.5 MB, the "App Speed" row 2) → Update 125 (\~20 KB, 28 queries).

* **Chapter quiz backfill (Update 34 workaround)** and **one quiz per chapter** → closed by Update 123
  (19/10/2026): chapters carry `quizzes[]` and keep every quiz, and the backfill was deleted. The quiz
  missing under lessonless chapters was a draft quiz, which the backfill exposed through
  `GET /chapter/{id}`; published quizzes always came back from `GET /training/{id}`.

* **#6** My Content forced to 1536px → fixed by Update 76.

* **#18** Classroom by-student payload exposure → fixed by Update 73 (whitelisted persons).

* **#28** Certificates showing the default badge → fixed by Update 82 (`training.badge_logo`
  eager-loaded).

* **#20** Dashboard "Edit profile information" opening the cart → the widget was **deleted** in the
  08/10/2026 dead-code clean-up (removed, not fixed); the table row can be closed.

* **Marketplace leaks** (lesson content / answer keys / instructor PII in student network tabs) →
  closed by Updates 39 + 44 (`preview-*` endpoints) — the *hardening* of the old endpoints remains
  critical (see above).

* **Exam views always 0** → fixed by Update 47 (`quizzes.views_count` migration — remember to run
  `php artisan migrate` on each backend).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.