Skip to content

feat: data export, in-app feedback, and EN/DE localization (polish batch) - #32

Merged
timh8127 merged 13 commits into
mainfrom
feat/polish-batch-remaining
Jun 19, 2026
Merged

feat: data export, in-app feedback, and EN/DE localization (polish batch)#32
timh8127 merged 13 commits into
mainfrom
feat/polish-batch-remaining

Conversation

@timh8127

@timh8127 timh8127 commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Consolidated branch for the remaining WSIST Polish & Feature Batch items (Tasks 4, 5, 3), kept on one branch for review rather than stacked PRs.

Note on base: this branch is cut from the migration hotfix (#31) so it boots locally (main currently has the broken migration). The first commit here (af8eab2) is that hotfix — once #31 merges to main, it dedupes out of this diff. Please review/merge #31 first.

Task 4 — Self-service data export (nDSG / GDPR portability)

  • GET /api/export?format=csv|json, RequireAuthorization, resolves the user from their own auth claims and scopes the query strictly to that user id — one user's export can never contain another's.
  • CSV: RFC-4180 escaping + formula-injection mitigation (leading = + - @ etc. prefixed) + UTF-8 BOM for Excel/accented chars. JSON as a second format.
  • Export links in Settings. Tests cover cross-user isolation, inclusion of past graded tests, CSV header/escaping/injection.

Task 5 — In-app feedback / feature requests

  • Feedback entity (UserId FK cascade, Message ≤4000, Category/Status enums as ints, CreatedAt indexed) + migration.
  • FeedbackManagement service (validates/trims, caps length, newest-first admin projection). Authorization stays in the web layer.
  • /feedback page: any signed-in user submits (category + message); admin-only listing gated by Admin:Email config (override via Admin__Email env var; blank = nobody is admin). AuthenticatedComponentBase now exposes CurrentUserEmail.
  • Component named FeedbackPage to avoid colliding with the Feedback entity in Razor. 5 new tests.

Task 3 — EN/DE language switch

  • ASP.NET Core request localization with a single shared IStringLocalizer<SharedResource> resource (EN + DE satellite). One supported culture (en, so grade/date formatting never changes) but two supported UI cultures (en/de) — only the UI culture flips.
  • User.PreferredLanguage column + migration. DbRequestCultureProvider resolves a signed-in user's stored language ahead of the cookie/Accept-Language providers, so the choice persists across logout/login; null falls through to the browser language (first-login default).
  • /set-language/{culture} writes the culture cookie, persists the preference, and does an open-redirect-safe LocalRedirect. EN / DE toggle in every nav; <html lang> bound to the current UI culture.
  • Every page translated: Home, Study, Settings, Feedback, the marketing landing page + its client-side playground (landing.js carries its own EN/DE table keyed off <html lang>), Privacy, Terms, and the error/not-found pages. German is High German (ß spelling, not Swiss); legal sections rendered via MarkupString from static, developer-authored resource HTML.

Verification

  • dotnet build -c Release clean; dotnet test 32/32 green; dotnet csharpier clean.
  • Confirmed the EN resource compiles to WSIST.Web.Resources.SharedResource.resources and the de/ satellite assembly is emitted.

Reviewer notes

  • The pre-existing Title = "Some Test" modal default is left as-is (changing to empty risks an empty-submit crash — out of scope here).
  • Illustrative landing sample content (e.g. example test titles) is intentionally left literal; all real UI prose/labels are localized.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a user feedback system with a dedicated /feedback page; users can submit categorized messages, and admins can review and update feedback statuses.
    • Added test export via /api/export supporting CSV and JSON.
    • Added multi-language support (English/German) with a language toggle and persisted preferred language selection.
    • Localized the user interface across core pages, including feedback, settings, and privacy/terms.
  • Bug Fixes
    • Improved feedback listing to use deterministic newest-first ordering.
  • Tests
    • Added unit tests covering feedback submission/status updates and CSV export formatting + formula-injection mitigation.

timh8127 and others added 10 commits June 17, 2026 08:26
GetTestExport(userId) returns every test for the user — past and future,
graded or not — with subject names resolved and enum values rendered to
readable text, strictly scoped to the given user id. TestExporter.ToCsv
serialises the rows as RFC 4180 CSV (CRLF, quoted fields) with formula-injection
mitigation on the user-controlled Title and Subject columns. Tests cover
user isolation, inclusion of past graded tests, CSV escaping, and injection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /api/export?format=csv|json returns the authenticated user's tests as a
downloadable file (CSV default, JSON optional), resolving the user from their
own claims so the export can never include another user's rows — nDSG / GDPR
data portability. The CSV carries a UTF-8 BOM so spreadsheets render accented
characters. Settings gains a "Data" block with CSV/JSON download links via a
plain HTTP endpoint, avoiding a file download over the Blazor SignalR circuit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New Feedback table for in-app feedback / feature requests: Id, UserId
(FK to Users, cascade delete), Message (varchar 4000), Category and
Status enums stored as ints (consistent with Test.Volume), and a
CreatedAt timestamp indexed for the newest-first admin listing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FeedbackManagement.Submit validates and trims the message, enforces the
4000-char column cap and a defined category, then persists an Open
feedback row. GetAll returns a newest-first projection (with submitter
name/email) for the admin listing. Authorization is left to the web
layer — the engine has no notion of who the admin is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A /feedback page (linked from Settings) lets any logged-in user submit
feedback with a category and message. Submissions go through
FeedbackManagement; the page maps validation errors to inline messages
and clears the form on success.

The full-submissions listing on the same page is gated to the configured
admin account (Admin:Email, env-overridable as Admin__Email); a blank
value means nobody is admin. AuthenticatedComponentBase now exposes
CurrentUserEmail so pages can do this owner check without re-reading the
auth state. /feedback is added to the auth-protected path list.

The page component is named FeedbackPage to avoid colliding with the
WSIST.Engine.Feedback entity type in Razor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds tests for FeedbackManagement: trimmed/Open persistence, empty and
over-long message rejection, undefined-category rejection, and the
newest-first GetAll projection with resolved submitter name/email.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sets up ASP.NET Core request localization with English and German UI
cultures while pinning the formatting culture to English, so switching
language never changes how grades/dates are parsed or displayed.

- AddLocalization + RequestLocalizationOptions (supported UI cultures
  en/de; default en).
- DbRequestCultureProvider resolves a signed-in user's stored
  PreferredLanguage ahead of the cookie and Accept-Language providers,
  so the choice persists across logout/login. A null preference falls
  through to the browser language (first-login default).
- User.PreferredLanguage column (+ migration) and engine methods to
  read/update it.
- /set-language/{culture} endpoint writes the culture cookie, persists
  the preference for signed-in users, and does an open-redirect-safe
  local redirect back.
- LanguageToggle component (EN / DE) and <html lang> bound to the
  current UI culture.
- SharedResource marker for a single shared IStringLocalizer resource.

No user-facing strings are translated yet; that follows per page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the shared EN/DE resource files and routes every user-facing string
on the authenticated app pages — plus the not-found and error pages —
through IStringLocalizer<SharedResource>. The EN/DE language toggle is
placed in each page's top nav.

- Volume/understanding levels render from Level_* keys (one source for
  selects, tables and the study "because" text), so the Test enum
  helpers are no longer shown raw.
- Study's composed "because" sentence and score breakdown are built from
  parameterized resource templates.
- Pluralized day/test counts use explicit one/many keys.
- German copy is High German with consistent terminology (Prüfung, Fach,
  Note, Umfang, Verständnis).

Landing page and legal pages follow next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Routes every string on the marketing landing page through
IStringLocalizer and adds the EN/DE toggle to its nav. The interactive
playground (landing.js) runs client-side and can't use the server
localizer, so it carries its own EN/DE table for the level labels, day
labels and verdict text, picked from <html lang>.

Illustrative sample content (example test titles like "Algebra II —
Quadratics") is left as-is; subject-name demo tokens, day/points labels
and all prose are localized.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each legal section is stored as a static, developer-authored HTML block
in the resource files and rendered via MarkupString, so inline emphasis
and links survive translation while keeping the key count small. The
German versions are High German (ß spelling, not Swiss) with proper
legal phrasing (DSGVO / Schweizer nDSG). Both legal pages get the EN/DE
toggle in their nav.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@timh8127

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 78d500ab-54ed-48a5-8cc4-254b9d0a4cae

📥 Commits

Reviewing files that changed from the base of the PR and between e171abe and 5e79c01.

📒 Files selected for processing (7)
  • WSIST/WSIST.Engine/FeedbackManagement.cs
  • WSIST/WSIST.UnitTests/UnitTests.cs
  • WSIST/WSIST.Web/Components/Pages/FeedbackPage.razor
  • WSIST/WSIST.Web/Components/Pages/FeedbackPage.razor.cs
  • WSIST/WSIST.Web/Resources/SharedResource.de.resx
  • WSIST/WSIST.Web/Resources/SharedResource.resx
  • WSIST/WSIST.Web/wwwroot/app.css
 __________________________________________________
< Your stack overflowed; I brought a bigger stack. >
 --------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
📝 Walkthrough

Walkthrough

Three independent features land together: a Feedback submission system (model, EF migrations, service, admin UI page with status updates); a CSV/JSON test export pipeline (TestExporter with formula-injection mitigation, /api/export endpoint, and Settings UI links); and full EN/DE localization (SharedResource RESX files, DB-stored preferred language, DbRequestCultureProvider, LanguageToggle component, /set-language endpoint, and all UI pages converted to use IStringLocalizer<SharedResource> with client-side landing page localization).

Changes

Feedback Feature

Layer / File(s) Summary
Feedback model, EF config, and migrations
WSIST.Engine/Feedback.cs, WSIST.Engine/WsistContext.cs, WSIST.Engine/Migrations/20260617062848_AddFeedbackTable.*, WSIST.Engine/Migrations/WsistContextModelSnapshot.cs
Defines the Feedback class with FeedbackCategory (Bug, Feature, Other) and FeedbackStatus (Open, Reviewed, Closed) nested enums, registers the Feedbacks DbSet, configures cascade delete and CreatedAt index in OnModelCreating, and adds the AddFeedbackTable EF Core migration with model snapshot entries.
FeedbackManagement service
WSIST.Engine/FeedbackManagement.cs
Implements Submit (trim/validate message 4000-char limit, persist with UTC CreatedAt and Open status), GetAll (newest-first ordering with deterministic Id tie-break, null-safe FeedbackView projection), and UpdateStatus (admin status changes with existence check).
FeedbackPage Razor component, admin gating, and protected paths
WSIST.Web/Components/Pages/FeedbackPage.razor, FeedbackPage.razor.cs, WSIST.Web/Components/Pages/AuthenticatedComponentBase.cs, WSIST.Web/Program.cs, WSIST.Web/appsettings.json
Exposes CurrentUserEmail on AuthenticatedComponentBase, adds the /feedback interactive page with submission form and admin feedback listing gated by Admin:Email config (case-insensitive comparison), registers FeedbackManagement in DI, adds /feedback to protected auth-required redirect paths, and configures the Admin section in appsettings.
Feedback UI styling
WSIST.Web/wwwroot/app.css
Adds CSS for feedback form container, label/textarea sizing, action row alignment, empty-state text, feedback item rows with category color badges (bug/feature), and timestamp display.
Feedback unit tests
WSIST.UnitTests/UnitTests.cs
Covers Submit whitespace trimming and persistence, empty/too-long/undefined-category ArgumentException validation, GetAll newest-first ordering with resolved submitter name/email, and UpdateStatus persistence and missing-row return value.

Test Export Feature

Layer / File(s) Summary
TestExportRow, TestExporter.ToCsv, and GetTestExport
WSIST.Engine/TestExporter.cs, WSIST.Engine/TestManagement.cs
Adds the TestExportRow typed record, fixed CSV header (Title/Subject/DueDate/Volume/Understanding/Grade), ToCsv method with CRLF line endings, invariant-culture formatting, RFC 4180 quoting/escaping, and formula-injection prefix neutralization (=, +, -, @, tab, CR); implements GetTestExport returning user-scoped rows ordered by due date then title with resolved subject names and readable enum values.
/api/export endpoint and Settings export UI
WSIST.Web/Program.cs, WSIST.Web/Components/Pages/Settings.razor
Adds the authorized GET /api/export endpoint returning pretty-printed JSON (format=json) or UTF-8-BOM-prefixed CSV (default), scoped to the authenticated user; adds CSV/JSON export links to the Settings Data section.
Test export and CSV unit tests
WSIST.UnitTests/UnitTests.cs
Tests GetTestExport user scoping and subject name resolution with grade/volume, ToCsv header presence and date/grade formatting, RFC 4180 delimiter/quote escaping, and spreadsheet formula-injection prefix mitigation.

Localization (EN/DE) and Preferred Language

Layer / File(s) Summary
SharedResource marker and RESX definitions
WSIST.Web/SharedResource.cs, WSIST.Web/Resources/SharedResource.resx, WSIST.Web/Resources/SharedResource.de.resx, WSIST.Web/Components/_Imports.razor
Adds the SharedResource marker class and complete English/German RESX files with keys for common UI, all page-specific labels, landing marketing copy, HTML-encoded Privacy Policy and Terms sections, and error pages; adds global Razor using directives for Authorization and Localization.
User.PreferredLanguage DB column and migration
WSIST.Engine/User.cs, WSIST.Engine/WsistContext.cs, WSIST.Engine/Migrations/20260617064345_AddUserPreferredLanguage.*, WSIST.Engine/Migrations/WsistContextModelSnapshot.cs
Adds a nullable PreferredLanguage string property to User, configures the max-length-5 column mapping, adds the AddUserPreferredLanguage EF Core migration, and updates the model snapshot.
DbRequestCultureProvider, /set-language endpoint, and preferred language methods
WSIST.Web/DbRequestCultureProvider.cs, WSIST.Web/Program.cs, WSIST.Engine/TestManagement.cs
Adds UpdatePreferredLanguage and GetPreferredLanguageByEmailAsync to TestManagement, introduces DbRequestCultureProvider reading DB-stored culture for authenticated users, adds the GET /set-language/{culture} endpoint validating en/de, persisting via cookie and DB, and performing safe local redirect, and configures localization middleware with UI cultures and provider ordering.
LanguageToggle component, App.razor dynamic lang, and CSS
WSIST.Web/Components/LanguageToggle.razor, WSIST.Web/Components/App.razor, WSIST.Web/wwwroot/app.css
Adds the LanguageToggle.razor component rendering EN/DE links to /set-language/{culture} with active styling based on current culture, updates App.razor to set root html lang attribute dynamically, and adds CSS styles for language toggle layout and landing nav actions.
All page UI strings localized
WSIST.Web/Components/Pages/Home.razor, Home.razor.cs, Study.razor, Study.razor.cs, Login.razor, Settings.razor, Settings.razor.cs, Privacy.razor, Terms.razor, NotFound.razor, Error.razor
Replaces all hardcoded UI strings across every page with localizer[...] key lookups; adds IStringLocalizer<SharedResource> constructor parameters to Home, Study, Settings, and FeedbackPage code-behind; replaces static Privacy/Terms HTML with MarkupString localizer entries; adds LanguageToggle to Privacy, Terms, and Settings navigation; switches understanding option rendering from helper methods to localized level keys.
Client-side landing page localization
WSIST.Web/wwwroot/landing.js
Adds STRINGS dictionary for en and de, selects active language via document.documentElement.lang, derives LEVEL_LABELS from selected language, and updates dayLabel and verdictFor functions to return localized strings.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant SetLang as GET /set-language/{culture}
  participant TestMgmt as TestManagement
  participant DB as Users (MySQL)
  participant DbProvider as DbRequestCultureProvider
  participant Middleware as UseRequestLocalization
  participant Page as Blazor Page

  Browser->>SetLang: GET /set-language/de?redirectUri=/home
  SetLang->>SetLang: Validate culture (en or de only)
  SetLang->>Browser: Set localization cookie (de)
  SetLang->>TestMgmt: UpdatePreferredLanguage(userId, "de")
  TestMgmt->>DB: UPDATE Users SET PreferredLanguage = "de"
  SetLang-->>Browser: Redirect to /home

  Note over Browser,Page: Subsequent request with authentication
  Browser->>Middleware: GET /home (auth cookie + locale cookie)
  Middleware->>DbProvider: DetermineProviderCultureResult(httpContext)
  DbProvider->>TestMgmt: GetPreferredLanguageByEmail(email)
  TestMgmt->>DB: SELECT PreferredLanguage FROM Users
  DB-->>DbProvider: "de"
  DbProvider-->>Middleware: ProviderCultureResult("de")
  Middleware->>Page: Render with CultureInfo "de"
  Page-->>Browser: German UI via IStringLocalizer<SharedResource>
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 Hop, hop — three hats at once I wear!
Feedback forms and CSV flair,
Deutsch und Englisch, side by side,
A LanguageToggle for the ride.
Migrations run, the columns grew,
A bunny built it all brand new! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: data export, in-app feedback, and EN/DE localization (polish batch)' accurately and comprehensively captures the three main feature areas implemented: data export (Task 4), in-app feedback (Task 5), and English/German localization (Task 3).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/polish-batch-remaining

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (1)
WSIST/WSIST.Engine/TestManagement.cs (1)

292-296: ⚡ Quick win

Move ordering before AsEnumerable() to keep sorting in SQL.

OrderBy/ThenBy currently runs in-memory after materialization. Push ordering to the DB and switch to in-memory only for the helper-based projection.

Proposed refactor
-        return context
-            .Tests.Where(t => t.UserId == userId)
-            .AsEnumerable()
-            .OrderBy(t => t.DueDate)
-            .ThenBy(t => t.Title)
+        return context
+            .Tests.Where(t => t.UserId == userId)
+            .OrderBy(t => t.DueDate)
+            .ThenBy(t => t.Title)
+            .AsEnumerable()
             .Select(t => new TestExportRow(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@WSIST/WSIST.Engine/TestManagement.cs` around lines 292 - 296, The LINQ query
in the method is performing sorting operations in-memory after materializing
data with AsEnumerable(), which is inefficient. Move the OrderBy and ThenBy
calls before the AsEnumerable() call so that the sorting happens at the database
level via SQL. The corrected order should be: apply the Where filter, then
OrderBy DueDate, then ThenBy Title, and only then call AsEnumerable() for any
remaining in-memory operations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@WSIST/WSIST.Engine/FeedbackManagement.cs`:
- Around line 49-52: The OrderByDescending(f => f.CreatedAt) call in the
Feedbacks query sorts only by creation timestamp, which produces unpredictable
ordering when multiple records have identical CreatedAt values. Add a secondary
sort key using ThenByDescending or ThenBy (with the Id field) after the
OrderByDescending call to ensure deterministic and stable ordering when
timestamps are equal.

In `@WSIST/WSIST.Web/appsettings.json`:
- Around line 9-12: The Admin.Email configuration in appsettings.json contains a
hard-coded personal email address (tim.an.zurich@gmail.com) which poses a
security risk if the Admin__Email environment variable override is not set
during production deployment. Replace the real email address with a placeholder
value such as "admin@example.com" or an empty string that clearly indicates this
must be overridden via environment variables, ensuring that no actual personal
email with admin access is left as the default.

In `@WSIST/WSIST.Web/Components/LanguageToggle.razor`:
- Line 7: The aria-label attribute on the language-toggle div element in
LanguageToggle.razor is hardcoded as "Language" in English, causing non-English
users to receive mixed-language screen-reader output. Replace the hardcoded
"Language" string with a localized resource key or value that will display the
appropriate translated label based on the current UI language. Use your
application's localization service or resource mechanism to bind the aria-label
to the correct translated string.

In `@WSIST/WSIST.Web/Components/Pages/FeedbackPage.razor.cs`:
- Around line 53-59: The ArgumentException catch block in FeedbackPage.razor.cs
incorrectly assumes all ArgumentException cases correspond to empty messages and
maps them all to Feedback_EmptyError, but engine validation can also throw
ArgumentException for over-length messages and invalid category values. Refactor
the catch block to inspect the exception details (message or exception type) and
map each validation failure case to its corresponding localized error message
key. For the empty message case use Feedback_EmptyError, and create or use
separate localization keys for over-length and invalid category errors to ensure
users see accurate error messages.

In `@WSIST/WSIST.Web/Components/Pages/Login.razor`:
- Line 317: The hardcoded German string "WSIST sagt" in the h3 element bypasses
the localizer and will display in German even when the UI is set to English.
Replace the hardcoded "WSIST sagt" string with a localized reference using the
`@Localizer` syntax (similar to how `@Localizer`["Landing_Soon"] is used in the same
line). Create a corresponding localization key for this phrase and apply it
consistently wherever this text appears, including the location noted on line
330.

In `@WSIST/WSIST.Web/DbRequestCultureProvider.cs`:
- Around line 15-31: The DetermineProviderCultureResult method is blocking on
synchronous database I/O by calling GetPreferredLanguageByEmail() synchronously
for every authenticated request in the pipeline. Make this method async by
changing its implementation to use await, then add a new async method
GetPreferredLanguageByEmailAsync in the TestManagement class that uses
FirstOrDefaultAsync() instead of synchronous database operations, and update the
call in DetermineProviderCultureResult to await this new async variant instead
of the synchronous GetPreferredLanguageByEmail method.

In `@WSIST/WSIST.Web/Resources/SharedResource.de.resx`:
- Around line 169-170: The Settings_ExportSub value in the German resource file
contains an incomplete German sentence ending with "einschließlich vergangener
benoteter." which lacks a noun object to be grammatically correct. Update the
value attribute of the Settings_ExportSub data element to complete the German
phrase by adding the appropriate noun (such as "Prüfungen") after "benoteter" so
the sentence reads properly and makes sense to German-speaking users.

In `@WSIST/WSIST.Web/wwwroot/app.css`:
- Around line 1147-1152: In the `.feedback-item-message` CSS class, replace the
deprecated `word-break: break-word` property with `overflow-wrap: anywhere`.
This is the modern standard-compliant approach for handling word overflow and
text wrapping. Remove the deprecated property entirely and add the new
overflow-wrap property in its place to maintain the desired text wrapping
behavior.

---

Nitpick comments:
In `@WSIST/WSIST.Engine/TestManagement.cs`:
- Around line 292-296: The LINQ query in the method is performing sorting
operations in-memory after materializing data with AsEnumerable(), which is
inefficient. Move the OrderBy and ThenBy calls before the AsEnumerable() call so
that the sorting happens at the database level via SQL. The corrected order
should be: apply the Where filter, then OrderBy DueDate, then ThenBy Title, and
only then call AsEnumerable() for any remaining in-memory operations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c3388268-07cf-4063-a896-ace63aa4674a

📥 Commits

Reviewing files that changed from the base of the PR and between 401108b and e9e21c2.

📒 Files selected for processing (37)
  • WSIST/WSIST.Engine/Feedback.cs
  • WSIST/WSIST.Engine/FeedbackManagement.cs
  • WSIST/WSIST.Engine/Migrations/20260617062848_AddFeedbackTable.Designer.cs
  • WSIST/WSIST.Engine/Migrations/20260617062848_AddFeedbackTable.cs
  • WSIST/WSIST.Engine/Migrations/20260617064345_AddUserPreferredLanguage.Designer.cs
  • WSIST/WSIST.Engine/Migrations/20260617064345_AddUserPreferredLanguage.cs
  • WSIST/WSIST.Engine/Migrations/WsistContextModelSnapshot.cs
  • WSIST/WSIST.Engine/TestExporter.cs
  • WSIST/WSIST.Engine/TestManagement.cs
  • WSIST/WSIST.Engine/User.cs
  • WSIST/WSIST.Engine/WsistContext.cs
  • WSIST/WSIST.UnitTests/UnitTests.cs
  • WSIST/WSIST.Web/Components/App.razor
  • WSIST/WSIST.Web/Components/LanguageToggle.razor
  • WSIST/WSIST.Web/Components/Pages/AuthenticatedComponentBase.cs
  • WSIST/WSIST.Web/Components/Pages/Error.razor
  • WSIST/WSIST.Web/Components/Pages/FeedbackPage.razor
  • WSIST/WSIST.Web/Components/Pages/FeedbackPage.razor.cs
  • WSIST/WSIST.Web/Components/Pages/Home.razor
  • WSIST/WSIST.Web/Components/Pages/Home.razor.cs
  • WSIST/WSIST.Web/Components/Pages/Login.razor
  • WSIST/WSIST.Web/Components/Pages/NotFound.razor
  • WSIST/WSIST.Web/Components/Pages/Privacy.razor
  • WSIST/WSIST.Web/Components/Pages/Settings.razor
  • WSIST/WSIST.Web/Components/Pages/Settings.razor.cs
  • WSIST/WSIST.Web/Components/Pages/Study.razor
  • WSIST/WSIST.Web/Components/Pages/Study.razor.cs
  • WSIST/WSIST.Web/Components/Pages/Terms.razor
  • WSIST/WSIST.Web/Components/_Imports.razor
  • WSIST/WSIST.Web/DbRequestCultureProvider.cs
  • WSIST/WSIST.Web/Program.cs
  • WSIST/WSIST.Web/Resources/SharedResource.de.resx
  • WSIST/WSIST.Web/Resources/SharedResource.resx
  • WSIST/WSIST.Web/SharedResource.cs
  • WSIST/WSIST.Web/appsettings.json
  • WSIST/WSIST.Web/wwwroot/app.css
  • WSIST/WSIST.Web/wwwroot/landing.js

Comment thread WSIST/WSIST.Engine/FeedbackManagement.cs
Comment thread WSIST/WSIST.Web/appsettings.json
Comment thread WSIST/WSIST.Web/Components/LanguageToggle.razor Outdated
Comment thread WSIST/WSIST.Web/Components/Pages/FeedbackPage.razor.cs
Comment thread WSIST/WSIST.Web/Components/Pages/Login.razor Outdated
Comment thread WSIST/WSIST.Web/DbRequestCultureProvider.cs Outdated
Comment thread WSIST/WSIST.Web/Resources/SharedResource.de.resx Outdated
Comment thread WSIST/WSIST.Web/wwwroot/app.css
- Blank the committed Admin:Email default (was a real mailbox); the
  admin email now comes from Admin__Email / appsettings.Development.json.
  A blank value means nobody is admin.
- Make DbRequestCultureProvider async (GetPreferredLanguageByEmailAsync
  via FirstOrDefaultAsync) so the request-localization pipeline no longer
  blocks on synchronous DB I/O for every authenticated request.
- Deterministic admin feedback ordering: tie-break on Id after CreatedAt.
- Map non-empty feedback validation failures to a generic localized
  error instead of always showing the empty-message text.
- Localize the language-toggle aria-label.
- Localize the last hardcoded landing strings ("WSIST sagt" and the
  manifesto attribution).
- Complete the German export subtitle sentence.
- Replace deprecated word-break: break-word with overflow-wrap: anywhere.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- FeedbackManagement.UpdateStatus(id, status) lets the admin move a
  submission through Open/Reviewed/Closed; the listing now renders a
  per-item status dropdown that saves on change. Tests cover the update
  and the missing-row case.
- The WSIST logo/title in the Settings, Study, and Feedback top navs is
  now a link to "/" (was a non-clickable div).

Note: feedback is still DB-only — there is no reply-to-user channel.
Replying to a submitter would need email/notification delivery, which
isn't built yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds FeedbackManagement.GetForUser(userId) and a "Your feedback" section
on /feedback, visible to every signed-in user, listing their own
submissions (newest first) with category, status, date and message —
read-only; only the admin can change status. Refreshes after submitting
and after an admin status change. Test covers per-user isolation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
WSIST/WSIST.Engine/TestManagement.cs (1)

301-303: ⚡ Quick win

Push export ordering to SQL before materialization.

Line 301 materializes early, so sorting runs in memory. Move AsEnumerable() after OrderBy/ThenBy so DB handles ordering.

♻️ Proposed refactor
         return context
             .Tests.Where(t => t.UserId == userId)
-            .AsEnumerable()
             .OrderBy(t => t.DueDate)
             .ThenBy(t => t.Title)
+            .AsEnumerable()
             .Select(t => new TestExportRow(
                 t.Title,
                 subjects.TryGetValue(t.Subject, out var name) ? name : t.Subject.ToString(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@WSIST/WSIST.Engine/TestManagement.cs` around lines 301 - 303, The
AsEnumerable() call is materializing the data into memory before the OrderBy and
ThenBy operations, causing sorting to happen in memory instead of at the
database level. Move the AsEnumerable() call to after the OrderBy(t =>
t.DueDate) and ThenBy(t => t.Title) operations so that the database handles the
sorting before the data is materialized, improving query performance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@WSIST/WSIST.Engine/TestManagement.cs`:
- Around line 301-303: The AsEnumerable() call is materializing the data into
memory before the OrderBy and ThenBy operations, causing sorting to happen in
memory instead of at the database level. Move the AsEnumerable() call to after
the OrderBy(t => t.DueDate) and ThenBy(t => t.Title) operations so that the
database handles the sorting before the data is materialized, improving query
performance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c68c4bca-8cb6-4ee7-a7ec-077c3bb99e5a

📥 Commits

Reviewing files that changed from the base of the PR and between e9e21c2 and e171abe.

📒 Files selected for processing (14)
  • WSIST/WSIST.Engine/FeedbackManagement.cs
  • WSIST/WSIST.Engine/TestManagement.cs
  • WSIST/WSIST.UnitTests/UnitTests.cs
  • WSIST/WSIST.Web/Components/LanguageToggle.razor
  • WSIST/WSIST.Web/Components/Pages/FeedbackPage.razor
  • WSIST/WSIST.Web/Components/Pages/FeedbackPage.razor.cs
  • WSIST/WSIST.Web/Components/Pages/Login.razor
  • WSIST/WSIST.Web/Components/Pages/Settings.razor
  • WSIST/WSIST.Web/Components/Pages/Study.razor
  • WSIST/WSIST.Web/DbRequestCultureProvider.cs
  • WSIST/WSIST.Web/Resources/SharedResource.de.resx
  • WSIST/WSIST.Web/Resources/SharedResource.resx
  • WSIST/WSIST.Web/appsettings.json
  • WSIST/WSIST.Web/wwwroot/app.css
✅ Files skipped from review due to trivial changes (2)
  • WSIST/WSIST.Web/appsettings.json
  • WSIST/WSIST.Web/Resources/SharedResource.de.resx
🚧 Files skipped from review as they are similar to previous changes (10)
  • WSIST/WSIST.Web/Components/Pages/FeedbackPage.razor
  • WSIST/WSIST.Web/Components/LanguageToggle.razor
  • WSIST/WSIST.Web/wwwroot/app.css
  • WSIST/WSIST.Web/Components/Pages/Settings.razor
  • WSIST/WSIST.Web/Components/Pages/FeedbackPage.razor.cs
  • WSIST/WSIST.UnitTests/UnitTests.cs
  • WSIST/WSIST.Web/DbRequestCultureProvider.cs
  • WSIST/WSIST.Web/Resources/SharedResource.resx
  • WSIST/WSIST.Web/Components/Pages/Study.razor
  • WSIST/WSIST.Web/Components/Pages/Login.razor

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

@timh8127
timh8127 merged commit e523a4a into main Jun 19, 2026
3 checks passed
@timh8127
timh8127 deleted the feat/polish-batch-remaining branch June 27, 2026 20:36
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.

1 participant