Skip to content

Programming exercises: User can resolve pending conflict in Problem Statement editor after network interruptions is resolved#12529

Open
Elfari1028 wants to merge 4 commits intodevelopfrom
feature/synchronization/re-sync-problem-statement-editor-on-reconnection
Open

Programming exercises: User can resolve pending conflict in Problem Statement editor after network interruptions is resolved#12529
Elfari1028 wants to merge 4 commits intodevelopfrom
feature/synchronization/re-sync-problem-statement-editor-on-reconnection

Conversation

@Elfari1028
Copy link
Copy Markdown
Contributor

@Elfari1028 Elfari1028 commented Apr 14, 2026

Summary

Restore problem statement synchronization cleanly after WebSocket reconnects and surface unsaved-vs-latest conflicts in a dedicated diff view.

Checklist

General

Client

  • Important: I implemented the changes with a very good performance, prevented too many (unnecessary) REST calls and made sure the UI is responsive, even with large data (e.g. using paging).
  • I strictly followed the principle of data economy for all client-server REST calls.
  • I strictly followed the client coding guidelines.
  • I strictly followed the AET UI-UX guidelines.
  • Following the theming guidelines, I specified colors only in the theming variable files and checked that the changes look consistent in both the light and the dark theme.
  • I added multiple integration tests (Vitest) related to the features (with a high test coverage), while following the test guidelines.
  • I documented the TypeScript code using JSDoc style.
  • I added multiple screenshots/screencasts of my UI changes.
  • I translated all newly inserted strings into English and German.

Changes affecting Programming Exercises

  • High priority: I tested all changes and their related features with all corresponding user types on a test server configured with the integrated lifecycle setup (LocalVC and LocalCI).
  • I tested all changes and their related features with all corresponding user types on a test server configured with LocalVC and Jenkins.

Motivation and Context

Problem statement editing uses collaborative synchronization. After a temporary WebSocket disconnect, the local editor could remain out of sync with the latest shared state, and users had no guided way to compare their unsaved local text against the synchronized version after reconnecting.

Description

This PR adds reconnection handling for the programming exercise problem statement editor.

When the exercise editor detects a WebSocket reconnect, it snapshots the current local problem statement, tears down the Yjs binding, and initializes synchronization again from the current exercise state. After the initial sync finalizes, the component compares the locally snapshotted text with the newly synchronized content.

If both versions differ, the editor automatically switches into diff mode:

  • Left side: the user’s unsaved local version from before the reconnect
  • Right side: the latest synchronized version after reconnect

To support this, the PR introduces:

  • a reconnected$ stream in ExerciseEditorSyncService
  • reconnection-specific diff mode handling in ProgrammingExerciseEditableInstructionComponent
  • optional diff header label overrides in MarkdownEditorMonacoComponent
  • a Monaco API to replace the original diff pane content after entering diff mode
  • translated UI labels and a small action button for leaving the reconnection diff view

The PR also adds unit tests for the new reconnect detection stream.

Steps for Testing

Prerequisites:

  • 1 Instructor
  • 2 browser sessions for the same Instructor user or 2 Instructors with edit access
  • 1 existing Programming Exercise
  1. Log in to Artemis as an instructor and open the programming exercise problem statement editor in two browser sessions.
  2. In session A, edit the problem statement without saving.
  3. Simulate a temporary connection loss in session A.
  4. While session A is disconnected, change the problem statement in session B so the shared state diverges.
  5. Restore the WebSocket connection in session A.
  6. Verify that the editor in session A re-synchronizes automatically.
  7. Verify that a diff view opens when the pre-reconnect local version differs from the synchronized version.
  8. Verify that the diff labels describe the two sides as unsaved changes vs latest version.
  9. Verify that the user can inspect the diff and leave the reconnection diff view with the provided button.
  10. Verify that no diff view is shown when the local snapshot and synchronized content are identical after reconnect.

Exam Mode Testing

Prerequisites:

  • 1 Instructor
  • 1 Exam with a Programming Exercise
  1. Open the programming exercise in exam mode.
  2. Verify that the problem statement editor still renders correctly when not in the new reconnection diff flow.
  3. Verify that standard editing behavior is unchanged if no reconnect conflict occurs.

Review Progress

Performance Review

  • I (as a reviewer) confirm that the client changes (in particular related to REST calls and UI responsiveness) are implemented with a very good performance even for very large courses with more than 2000 students.
  • I (as a reviewer) confirm that the server changes (in particular related to database calls) are implemented with a very good performance even for very large courses with more than 2000 students.

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Exam Mode Test

  • Test 1
  • Test 2

Performance Tests

  • Test 1
  • Test 2

Test Coverage

Client

Class/File Line Coverage Lines Expects Ratio
exercise-editor-sync.service.ts not found (modified) 248 44 17.7
programming-exercise-editable-instruction.component.ts 86.15% 473 73 15.4
markdown-editor-monaco.component.ts 88.40% 758 76 10.0
monaco-editor.component.ts 87.74% 756 104 13.8

Last updated: 2026-04-20 08:02:17 UTC

Screenshots

Summary by CodeRabbit

  • New Features

    • Detects reconnection and shows a diff comparing your unsaved edits with the latest server version; editor switches to diff mode and populates the original side.
    • Adds a dismissible "Done" button to close the reconnection diff.
    • Adds localized labels for the reconnection diff view.
  • Tests

    • Expanded test coverage for reconnection transition detection and emission semantics.

…iff view

Adds a reconnection handler that detects when a user has unsaved local changes after a WebSocket reconnection. When the local snapshot differs from the newly synced content, the editor switches to diff mode showing the user's unsaved changes on the left and the latest synced version on the right, allowing selective application of changes.

- Add `reconnected$` observable to `ExerciseEditorSyncService` that emits on
@Elfari1028 Elfari1028 self-assigned this Apr 14, 2026
@github-project-automation github-project-automation bot moved this to Work In Progress in Artemis Development Apr 14, 2026
@Elfari1028 Elfari1028 marked this pull request as ready for review April 14, 2026 08:22
@Elfari1028 Elfari1028 requested review from a team and krusche as code owners April 14, 2026 08:22
@github-actions github-actions bot added client Pull requests that update TypeScript code. (Added Automatically!) exercise Pull requests that affect the corresponding module programming Pull requests that affect the corresponding module ready for review labels Apr 14, 2026
@github-actions
Copy link
Copy Markdown

@Elfari1028 Test coverage could not be fully measured because some tests failed. Please check the workflow logs for details.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

Walkthrough

Adds a reconnection observable and UI flow: on websocket reconnect after a prior connection, the editor snapshots local content, re-syncs server content, compares them, and conditionally shows a diff view with a dismiss action.

Changes

Cohort / File(s) Summary
Synchronization service & tests
src/main/webapp/app/exercise/synchronization/services/exercise-editor-sync.service.ts, src/main/webapp/app/exercise/synchronization/services/exercise-editor-sync.service.spec.ts
Added reconnected$: Observable<void> that emits on subsequent disconnected → connected transitions (excluding the initial connect). Added tests verifying emission semantics across connection cycles.
Instruction editor — logic
src/main/webapp/app/programming/manage/instructions-editor/programming-exercise-editable-instruction.component.ts
Subscribed to reconnected$, implemented handleReconnection(exerciseId) to snapshot editor text, reinitialize problem-statement sync, compare final server content, activate reconnection diff state, set translated labels, provide dismissReconnectionDiff(), and added effectiveMode computed plus cleanup of subscriptions.
Instruction editor — template
src/main/webapp/app/programming/manage/instructions-editor/programming-exercise-editable-instruction.component.html
Switched editor mode binding to effectiveMode(), added diff label override bindings, and rendered a conditional dismiss button for the reconnection diff UI.
Markdown / Monaco editor components
src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.ts, src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts
Introduced overrideable diff-label inputs and changed label selection to prefer overrides; added setDiffOriginalContent(content: string) to set the diff editor’s original/left pane programmatically.
i18n
src/main/webapp/i18n/en/programmingExercise.json, src/main/webapp/i18n/de/programmingExercise.json
Added reconnectionDiff translation keys (originalLabel, modifiedLabel, done) for English and German.
Test DI change
src/main/webapp/app/programming/manage/update/programming-exercise-update.component.spec.ts
Replaced generic mock provider with an explicit ExerciseEditorSyncService test double (provides reconnected$ observable and mocked connect/disconnect).

Sequence Diagram

sequenceDiagram
    participant WS as WebSocket
    participant SyncService as ExerciseEditorSyncService
    participant Editor as ProgrammingExerciseEditableInstructionComponent
    participant ProblemSync as ProblemStatementSyncService
    participant Monaco as MonacoEditorComponent

    WS->>SyncService: connectionState$ changes disconnected → connected
    SyncService->>Editor: reconnected$ emits
    Editor->>Editor: handleReconnection(exerciseId)
    Editor->>Editor: Snapshot local editor content
    Editor->>ProblemSync: tear down & re-init sync for exerciseId
    ProblemSync->>Editor: initialSyncFinalized$ emits (final server content)
    Editor->>Editor: Compare snapshot vs final server content
    alt content diverged
        Editor->>Editor: set reconnection labels & reconnectionDiffActive = true
        Editor->>Monaco: setDiffOriginalContent(snapshot)
        Monaco->>Monaco: update left/original diff pane
        Editor->>Monaco: render diff (mode='diff') with dismiss button
    else content identical
        Editor->>Editor: dismissReconnectionDiff() (no diff shown)
    end
    Note over Editor,Monaco: User clicks "Done" → dismissReconnectionDiff()
    Editor->>Editor: reconnectionDiffActive = false
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature: handling pending conflicts in the Problem Statement editor after network reconnections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 feature/synchronization/re-sync-problem-statement-editor-on-reconnection

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.

Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/webapp/app/exercise/synchronization/services/exercise-editor-sync.service.spec.ts`:
- Around line 472-518: Add a regression test to ensure the initial successful
connection isn't treated as a reconnect: in the reconnected$ spec, create a new
it() that subscribes to service.reconnected$, then push
connectionState$.next(new ConnectionState(false, false)) followed by
connectionState$.next(new ConnectionState(true, true)) and assert that the
subscription callback was not called (emit count remains 0); reference the
existing reconnected$ observable and the ConnectionState constructor to locate
where to add the test.

In
`@src/main/webapp/app/exercise/synchronization/services/exercise-editor-sync.service.ts`:
- Around line 236-254: The reconnected$ getter emits on the very first
false→true transition because it only checks the immediate pair; to restrict it
to true reconnects, add a service-level boolean flag (e.g., private
everConnected: boolean = false) that is set to true whenever
websocketService.connectionState emits connected === true, and then update
reconnected$'s filter to require everConnected to be true in addition to the
pairwise check (i.e., filter(([wasConnected, isConnected]) => everConnected &&
!wasConnected && isConnected)). Ensure everConnected is updated from
websocketService.connectionState subscriptions so the getter remains a pure
Observable creation.

In
`@src/main/webapp/app/programming/manage/instructions-editor/programming-exercise-editable-instruction.component.ts`:
- Around line 629-632: When the initialSyncFinalized$ subscriber sees snapshot
=== finalContent it currently returns early but fails to clear reconnection
state; update the callback in
programming-exercise-editable-instruction.component.ts (the subscription to
problemStatementSyncService.initialSyncFinalized$) to reset
reconnectionDiffActive and any override label/state (the component properties
managing the "override" labels and reconnection view) before returning so the
stale diff view and labels are cleared when there is no conflict.
- Around line 628-644: The one-off subscription to initialSyncFinalized$ (the
take(1) call) can outlive the reconnect that created it and consume a later
finalize event with the wrong snapshot; store the Subscription returned by
subscribe (e.g., this.initialSyncFinalizeSub) when you call
initialSyncFinalized$.pipe(take(1)).subscribe(...), unsubscribe that
Subscription before starting a new reconnect cycle (and set it to undefined),
and also unsubscribe it in ngOnDestroy to ensure stale callbacks cannot reopen
the diff with outdated data; keep the existing logic inside the subscription
(including calling this.markdownEditorMonaco?.setDiffOriginalContent(snapshot)).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a8720204-8ceb-4ead-93e2-3d5bbae9ea17

📥 Commits

Reviewing files that changed from the base of the PR and between fc9fcf0 and fd7bd20.

📒 Files selected for processing (8)
  • src/main/webapp/app/exercise/synchronization/services/exercise-editor-sync.service.spec.ts
  • src/main/webapp/app/exercise/synchronization/services/exercise-editor-sync.service.ts
  • src/main/webapp/app/programming/manage/instructions-editor/programming-exercise-editable-instruction.component.html
  • src/main/webapp/app/programming/manage/instructions-editor/programming-exercise-editable-instruction.component.ts
  • src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.ts
  • src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts
  • src/main/webapp/i18n/de/programmingExercise.json
  • src/main/webapp/i18n/en/programmingExercise.json

@github-project-automation github-project-automation bot moved this from Work In Progress to Ready For Review in Artemis Development Apr 14, 2026
Copy link
Copy Markdown
Contributor

@Claudia-Anthropica Claudia-Anthropica left a comment

Choose a reason for hiding this comment

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

@Elfari1028 Nice approach overall — the reconnection flow with snapshot-compare-diff is well thought out and the tests cover the new observable nicely. Two medium issues: the reconnected$ getter recreates a stateful pairwise() chain on every access, and the take(1) subscription inside handleReconnection is untracked, which can leak or double-fire on rapid reconnections. See inline comments.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 14, 2026

End-to-End Test Results

Phase Status Details
Phase 1 (Relevant) ❌ Failed
TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
Phase 1: E2E Test Report38 ran36 passed1 skipped1 failed11m 4s
Phase 2 (Remaining) ⏭ Skipped (Phase 1 failed)

Test Strategy: Two-phase execution

  • Phase 1: e2e/Login.spec.ts e2e/Logout.spec.ts e2e/SystemHealth.spec.ts e2e/course/CourseExercise.spec.ts e2e/exercise/ExerciseImport.spec.ts e2e/exercise/programming/
  • Phase 2: e2e/atlas/ e2e/course/CourseChannelMessages.spec.ts e2e/course/CourseDirectMessages.spec.ts e2e/course/CourseGroupChatMessages.spec.ts e2e/course/CourseManagement.spec.ts e2e/course/CourseMessageInteractions.spec.ts e2e/course/CourseOnboarding.spec.ts e2e/exam/ExamAssessment.spec.ts e2e/exam/ExamChecklists.spec.ts e2e/exam/ExamCreationDeletion.spec.ts e2e/exam/ExamDateVerification.spec.ts e2e/exam/ExamManagement.spec.ts e2e/exam/ExamParticipation.spec.ts e2e/exam/ExamResults.spec.ts e2e/exam/ExamTestRun.spec.ts e2e/exam/test-exam/ e2e/exercise/file-upload/ e2e/exercise/modeling/ e2e/exercise/quiz-exercise/ e2e/exercise/text/ e2e/lecture/

Overall: ❌ Phase 1 (relevant tests) failed

🔗 Workflow Run · 📊 Test Report

…nections and prevent memory leaks

- Change `reconnected$` from getter to readonly property to avoid creating multiple pairwise buffers
- Filter out initial connection by checking `wasEverConnectedBefore` flag
- Add test case verifying no emission on initial connect
- Add `reconnectionFinalizeSubscription` to properly clean up subscription in teardown
- Dismiss reconnection diff when snapshot matches final content after sync
Copy link
Copy Markdown
Contributor

@Claudia-Anthropica Claudia-Anthropica left a comment

Choose a reason for hiding this comment

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

@Elfari1028 Both medium issues are addressed — reconnected$ is now a proper readonly field and the finalize subscription is tracked and cleaned up. Nice work on the reconnection conflict flow. Approving.

@github-actions
Copy link
Copy Markdown

@Elfari1028 Test coverage could not be fully measured because some tests failed. Please check the workflow logs for details.

coderabbitai[bot]
coderabbitai bot previously approved these changes Apr 14, 2026
@github-actions
Copy link
Copy Markdown

@Elfari1028 Test coverage could not be fully measured because some tests failed. Please check the workflow logs for details.

@github-actions
Copy link
Copy Markdown

@Elfari1028 Test coverage has been automatically updated in the PR description.

@github-actions
Copy link
Copy Markdown

@Elfari1028 Your PR description needs attention before it can be reviewed:

Issues Found

  1. Screenshots are missing but this PR contains visual/UI changes

How to Fix

  • Add screenshots of the UI changes.

This check validates that your PR description follows the PR template. A complete description helps reviewers understand your changes and speeds up the review process.

Note: This description validation is an experimental feature. If you observe false positives, please send a DM with a link to the wrong comment to Patrick Bassner on Slack. Thank you!

Copy link
Copy Markdown
Contributor

@Claudia-Anthropica Claudia-Anthropica left a comment

Choose a reason for hiding this comment

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

@Elfari1028 Both medium issues from my previous review are properly fixed — reconnected$ is a readonly field now and the finalize subscription is tracked and cleaned up. The reconnection flow is solid: snapshot → teardown → re-init → compare → diff is clean and all the edge cases (no conflict, rapid reconnections, component destruction) are handled correctly. Nice work.

Copy link
Copy Markdown
Contributor

@HawKhiem HawKhiem left a comment

Choose a reason for hiding this comment

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

Code lgtm 👍. Will test on a test server tonight

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

Labels

client Pull requests that update TypeScript code. (Added Automatically!) exercise Pull requests that affect the corresponding module programming Pull requests that affect the corresponding module ready for review

Projects

Status: Ready For Review

Development

Successfully merging this pull request may close these issues.

3 participants