diff --git a/.upgradenotes/MDL-87662-2026012705202377.yml b/.upgradenotes/MDL-87662-2026012705202377.yml
new file mode 100644
index 00000000000..6a47f02a2f9
--- /dev/null
+++ b/.upgradenotes/MDL-87662-2026012705202377.yml
@@ -0,0 +1,12 @@
+issueNumber: MDL-87662
+notes:
+ theme:
+ - message: >-
+ The manual completion button has been moved to the activity header
+ to improve visibility and proximity to the activity name. A new
+ theme layout option, `completioninheader`, has been introduced to
+ control this behaviour and is enabled by default. Themes that set
+ completioninheader to false must manually override the relevant
+ template (such as `activity_header` or `activity_info`) to ensure
+ the completion information is displayed correctly.
+ type: improved
diff --git a/public/completion/tests/behat/behat_completion.php b/public/completion/tests/behat/behat_completion.php
index c1b77b3a336..39660d5f197 100644
--- a/public/completion/tests/behat/behat_completion.php
+++ b/public/completion/tests/behat/behat_completion.php
@@ -302,7 +302,7 @@ class behat_completion extends behat_base {
* @param string $activityname The activity name.
*/
public function the_manual_completion_button_for_activity_should_be_disabled(string $activityname): void {
- $selector = "div[data-region='activity-information'][data-activityname='$activityname'] button";
+ $selector = "button[data-activityname='$activityname'][data-completion='manual']";
$params = [$selector, "css_element"];
$this->execute("behat_general::the_element_should_be_disabled", $params);
@@ -315,7 +315,7 @@ class behat_completion extends behat_base {
* @param string $activityname The activity name.
*/
public function the_manual_completion_button_for_activity_should_not_exist(string $activityname): void {
- $selector = "div[data-region=activity-information][data-activityname='$activityname'] button";
+ $selector = "button[data-activityname='$activityname'][data-completion='manual']";
$params = [$selector, "css_element"];
$this->execute('behat_general::should_not_exist', $params);
@@ -328,7 +328,7 @@ class behat_completion extends behat_base {
* @param string $activityname The activity name.
*/
public function the_manual_completion_button_for_activity_should_exist(string $activityname): void {
- $selector = "div[data-region=activity-information][data-activityname='$activityname'] button";
+ $selector = "button[data-activityname='$activityname'][data-completion='manual']";
$params = [$selector, "css_element"];
$this->execute('behat_general::should_exist', $params);
diff --git a/public/course/templates/activity_info.mustache b/public/course/templates/activity_info.mustache
index db544a12b35..9282c4a3161 100644
--- a/public/course/templates/activity_info.mustache
+++ b/public/course/templates/activity_info.mustache
@@ -53,20 +53,15 @@
{{#hascompletion}}
{{#uservisible}}
-
- {{#isautomatic}}
+ {{#isautomatic}}
+
{{#completiondetails}}
{{> core_course/completion_automatic }}
{{/completiondetails}}
- {{/isautomatic}}
- {{^isautomatic}}
- {{#showmanualcompletion}}
- {{> core_course/completion_manual }}
- {{/showmanualcompletion}}
- {{/isautomatic}}
-
+
+ {{/isautomatic}}
{{/uservisible}}
{{/hascompletion}}
diff --git a/public/course/templates/completion_manual.mustache b/public/course/templates/completion_manual.mustache
index b2d1dddedbf..361b051baeb 100644
--- a/public/course/templates/completion_manual.mustache
+++ b/public/course/templates/completion_manual.mustache
@@ -35,6 +35,7 @@
data-toggletype="manual:undo"
data-cmid="{{cmid}}"
data-activityname="{{activityname}}"
+ data-completion="manual"
data-withavailability="{{withavailability}}" {{!
}}{{#accessibledescription}}{{!
}}title="{{.}}" {{!
@@ -55,6 +56,7 @@
data-toggletype="manual:mark-done"
data-cmid="{{cmid}}"
data-activityname="{{activityname}}"
+ data-completion="manual"
data-withavailability="{{withavailability}}" {{!
}}{{#accessibledescription}}{{!
}}title="{{.}}" {{!
@@ -72,6 +74,8 @@
{{^istrackeduser}}