Skip to content

Fix mark-all-present switch requiring two clicks after modal cancel#14552

Open
akolson wants to merge 2 commits intolearningequality:release-v0.19.xfrom
akolson:attendance-mark-all-double-click
Open

Fix mark-all-present switch requiring two clicks after modal cancel#14552
akolson wants to merge 2 commits intolearningequality:release-v0.19.xfrom
akolson:attendance-mark-all-double-click

Conversation

@akolson
Copy link
Copy Markdown
Member

@akolson akolson commented Apr 8, 2026

Summary

After clicking Go back in the Mark all N learners as present confirmation modal, the Mark all learners present switch required two clicks to re-open the modal instead of one.

Root cause: KSwitch optimistically toggles its internal checked state when clicked. handleMarkAllChange(true) was called but instead of updating the actual attendance state, a modal was shown. The :value binding (allPresent) stayed false both before the click and after the cancel — so Vue saw no prop change, never re-rendered the switch, and it remained visually on. The next click then fired @change(false) (a no-op), and only the second click fired @change(true) to re-open the modal.

Fix: Introduced a pendingMarkAll ref in useAttendanceForm.js that is set to true when the modal opens and back to false when the modal is cancelled or confirmed. The switch now binds to markAllPresent = computed(() => allPresent.value || pendingMarkAll.value). When the modal is cancelled, pendingMarkAll flips false, which changes the computed from true → false — giving Vue a real reactive update to re-render the KSwitch back to its unchecked state.

A regression test was added to AttendancePages.spec.js that reproduces the exact sequence: open modal → click Go back → verify switch is unchecked → verify one click re-opens the modal.

Screen Recording 2026-04-10 at 10 55 00

References

Fixes #14416

Reviewer guidance

  1. Install the build from https://github.com/learningequality/kolibri/releases/tag/v0.19.3-rc1
  2. Go to Coach > Class home > class > Mark attendance

Reproduce the original bug:

  • With some learners absent, click the Mark all learners present switch
  • In the confirmation modal, click Go back
  • Click the switch again — before this fix, the modal did not reopen; a second click was required

With this fix, one click after cancelling the modal is sufficient to re-open it.

AI usage

Implemented with Claude Code. The root cause analysis, fix, and regression test were AI-generated. I reviewed the generated code for correctness and unnecessary complexity, verified the approach against existing test patterns, and confirmed all 26 tests pass.

@github-actions github-actions bot added APP: Coach Re: Coach App (lessons, quizzes, groups, reports, etc.) DEV: frontend SIZE: small labels Apr 8, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

@akolson akolson force-pushed the attendance-mark-all-double-click branch 3 times, most recently from 83941b7 to 0018f86 Compare April 8, 2026 16:59
@akolson akolson marked this pull request as ready for review April 10, 2026 07:11
KSwitch optimistically toggles its internal state on click. When the
"mark all present" modal was cancelled, allPresent stayed false (unchanged),
so Vue never re-rendered the switch — leaving it visually on. The next
click then fired @change(false) (a no-op) instead of reopening the modal.

Introduce pendingMarkAll ref set true when the modal opens and false when
cancelled or confirmed. The switch binds to markAllSwitchValue (allPresent
|| pendingMarkAll), so cancelling creates a real true→false prop change
that forces the KSwitch to re-render back to unchecked.

Adds a regression test reproducing the exact cancel→single-click sequence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@akolson akolson force-pushed the attendance-mark-all-double-click branch from 0018f86 to 6abb4ae Compare April 10, 2026 07:31
@akolson akolson requested a review from rtibblesbot April 10, 2026 07:31
Copy link
Copy Markdown
Contributor

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

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

Clean, minimal fix for a subtle KSwitch reactive-update problem.

CI: All checks passing (Frontend tests, linting, Python unit tests).
Visual: No screenshots provided — behavioral fix; see suggestion below. Dev server unavailable at review time.

  • suggestion: A short screencast or GIF showing the fixed sequence (switch click → modal opens → Go back → switch is unchecked → single click reopens modal) would help future readers and manual QA. The regression test fully validates the behavior, but a visual recording reduces reviewer friction for this type of interaction bug.
  • praise: See inline comments.

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Reviewed the pull request diff checking for:

  • Correctness: bugs, edge cases, undocumented behavior, resource leaks, hardcoded values
  • Design: unnecessary complexity, naming, readability, comment accuracy, redundant state
  • Architecture: duplicated concerns, minimal interfaces, composition over inheritance
  • Testing: behavior-based assertions, mocks only at hard boundaries, accurate coverage
  • Completeness: missing dependencies, unupdated usages, i18n, accessibility, security
  • Principles: DRY (same reason to change), SRP, Rule of Three (no premature abstraction)
  • Checked CI status and linked issue acceptance criteria
  • For UI changes: inspected screenshots for layout, visual completeness, and consistency

// user has clicked "mark all present" and is seeing the confirmation modal (pendingMarkAll).
// When the modal is canceled, pendingMarkAll resets to false, which changes this computed
// from true → false and gives Vue a real prop change to re-render the KSwitch correctly.
const markAllPresent = computed(() => allPresent.value || pendingMarkAll.value);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

praise: The block comment (lines 99–102) is exactly the right level of detail — it explains the problem, the mechanism, and the cancellation invariant in a way that makes the computed immediately comprehensible without reading surrounding code. Future readers will thank you.

expect(buttons.length).toBe(2);
});

it('re-opens modal on a single click after "Go back" was clicked in the mark-all modal', async () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

praise: The regression test mirrors the bug report's step sequence precisely (open modal → Go back → assert switch is unchecked → assert one click reopens modal). Testing behavior rather than implementation details, and the inline comment correctly documents the pre-fix failure mode for future readers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

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

Good housekeeping commit — string migration is correct and CI still passes.

Prior findings

Unaddressed (re-raised below):

  • Screencast/GIF for the fixed interaction sequence (suggestion) — no reply, no code change

Stable (not re-raised):

  • Praise on useAttendanceForm.js:103 (block comment quality) — still applies
  • Praise on spec regression test structure — still applies

1/3 prior findings resolved. 1 re-raised below.


CI passing.

Visual: dev server unavailable; no screenshots in PR. New commit is spec-only — no new UI surface to capture.

  • suggestion: The prior suggestion about adding a screencast or GIF of the fixed interaction sequence (switch click → modal opens → Go back → switch unchecked → one click reopens modal) remains open. The regression test covers it functionally, but a visual recording would reduce friction for anyone manually QA-ing this kind of interaction bug on future visits.

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

import { coreString } from 'kolibri/uiText/commonCoreStrings';
// eslint-disable-next-line import/named
import useSnackbar, { useSnackbarMock } from 'kolibri/composables/useSnackbar';
import { attendanceStrings } from 'kolibri-common/strings/attendanceStrings';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

praise: Replacing all hard-coded English strings with coreString()/attendanceStrings.*$() calls is thorough and correct — verified each mapping against the source definitions in attendanceStrings.js and commonCoreStrings.js. The tests now survive string updates without false failures.

@akolson akolson requested a review from marcellamaki April 10, 2026 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

APP: Coach Re: Coach App (lessons, quizzes, groups, reports, etc.) DEV: frontend SIZE: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants