Hi Ihor,

Attached is the patch adding the subtree checkbox summary operators
("Xs", "Xs/", and "Xs%") to column view. I decided to keep the
COOKIE_DATA change separate and will follow up on that later to keep
this patch focused.

Best,
-- 
Slawomir Grochowski
>From 60c9933766b3fc769325b62b5e8dd755047e5415 Mon Sep 17 00:00:00 2001
From: Slawomir Grochowski <[email protected]>
Date: Sat, 5 Sep 2026 08:12:18 +0200
Subject: [PATCH] org-colview: Add subtree checkbox summary operators (Xs, Xs/,
 Xs%)

* lisp/org-colview.el (org-columns-summary-types-default): Add
"Xs", "Xs/", and "Xs%" summary types.
(org-columns--checkbox-operators): Include subtree summary types.
(org-columns--checkbox-subtree-operators): New constant.
(org-columns--subtree-checkbox-summary-p): New function.
(org-columns--compute-spec): Pass subtree checkbox values up the tree
when operator is a subtree checkbox operator.
* doc/org-manual.org (Column attributes): Document "Xs", "Xs/", and
"Xs%" in the summary types table, and describe the difference between
children-only and subtree checkbox summary operators with a table example.
* etc/ORG-NEWS (New subtree checkbox summary operators in column view):
Announce the new summary types.
* testing/lisp/test-org-colview.el
(test-org-colview/columns-subtree-checkbox-statistics): New test.
---
 doc/org-manual.org               | 23 ++++++++++
 etc/ORG-NEWS                     |  7 ++++
 lisp/org-colview.el              | 24 +++++++++--
 testing/lisp/test-org-colview.el | 72 ++++++++++++++++++++++++++++++++
 4 files changed, 123 insertions(+), 3 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 31d56594f..4d0b2f7d8 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -5789,6 +5789,9 @@ optional.  The individual parts have the following meaning:
   | =X=      | Checkbox status: =[X]= if all children are =[X]=.         |
   | =X/=     | Checkbox status: =[n/m]=.                             |
   | =X%=     | Checkbox status: =[n%]=.                              |
+  | =Xs=     | Checkbox status: =[X]= if all checkboxes in subtree are =[X]=. |
+  | =Xs/=    | Checkbox status: =[n/m]= for checkboxes in subtree.   |
+  | =Xs%=    | Checkbox status: =[n%]= for checkboxes in subtree.    |
   | =:=      | Sum times (HH:MM); plain numbers are minutes.         |
   | =:min=   | Smallest time value in column.                        |
   | =:max=   | Largest time value.                                   |
@@ -5802,6 +5805,26 @@ optional.  The individual parts have the following meaning:
   You can also define custom summary types by setting
   ~org-columns-summary-types~.
 
+The checkbox summary types (=X=, =X/=, =X%= vs =Xs=, =Xs/=, =Xs%=)
+differ in how they aggregate deeper levels of the outline tree.
+The operators =X=, =X/=, and =X%= only summarize children: if a
+subheading has children of its own, it is treated as a single completed
+checkbox only when all of its children are complete.  In contrast, the
+subtree operators =Xs=, =Xs/=, and =Xs%= count all checkboxes across
+the entire subtree.
+
+For example, given a column displaying checkbox status:
+
+| Outline      | ={X/}=  | ={Xs/}= |
+|--------------+---------+---------|
+| =* Top=      | =[1/2]= | =[3/4]= |
+| =** Sub 1=   | =[1/2]= | =[1/2]= |
+| =*** Task 1= | =[X]=   | =[X]=   |
+| =*** Task 2= | =[ ]=   | =[ ]=   |
+| =** Sub 2=   | =[2/2]= | =[2/2]= |
+| =*** Task 3= | =[X]=   | =[X]=   |
+| =*** Task 4= | =[X]=   | =[X]=   |
+
 The =est+= summary type requires further explanation.  It is used for
 combining estimates, expressed as low-high ranges.  For example,
 instead of estimating that a particular task will take 5 days, you might
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 6b277eec7..a53ae9af7 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -78,6 +78,13 @@ to collect statistics across the entire subtree instead of only children.
 The previous keyword =recursive= remains supported for backward
 compatibility.
 
+*** New subtree checkbox summary operators in column view
+
+Column view now supports subtree checkbox summary types: =Xs=,
+=Xs/=, and =Xs%=.  Unlike their children-only counterparts (=X=,
+=X/=, =X%=), which only summarize children, these operators
+count all checkboxes across the entire subtree.
+
 *** Link targets can be used as search option in =id:= links
 
 When storing an =id:= link to target, the target will now be used as a
diff --git a/lisp/org-colview.el b/lisp/org-colview.el
index 290b9b3dc..65ddf3822 100644
--- a/lisp/org-colview.el
+++ b/lisp/org-colview.el
@@ -141,6 +141,9 @@ For more information, see `org-columns-dblock-write-default'."
     ("X"     . org-columns--summary-checkbox)
     ("X/"    . org-columns--summary-checkbox-count)
     ("X%"    . org-columns--summary-checkbox-percent)
+    ("Xs"    . org-columns--summary-checkbox)
+    ("Xs/"   . org-columns--summary-checkbox-count)
+    ("Xs%"   . org-columns--summary-checkbox-percent)
     ("max"   . org-columns--summary-max)
     ("mean"  . org-columns--summary-mean)
     ("min"   . org-columns--summary-min)
@@ -155,6 +158,13 @@ For more information, see `org-columns-dblock-write-default'."
   "Map operators to summary functions.
 See `org-columns-summary-types' for details.")
 
+(defconst org-columns--checkbox-subtree-operators '("Xs" "Xs/" "Xs%")
+  "Summary operators computing a checkbox summary of a whole subtree.")
+
+(defconst org-columns--checkbox-operators
+  (append '("X" "X/" "X%") org-columns--checkbox-subtree-operators)
+  "Summary operators computing a checkbox summary.")
+
 
 ;;;; Column View
 
@@ -993,7 +1003,7 @@ an integer, select that value."
 		 (or (org-property-get-allowed-values pom key)
 		     (and (member (org-columns--spec-operator
 				   (org-columns--spec-at-point))
-				  '("X" "X/" "X%"))
+				  org-columns--checkbox-operators)
 			  org-columns-checkbox-allowed-values)
 		     (org-colview-construct-allowed-dates value))))
 	    (if previous (reverse all) all))))
@@ -1584,6 +1594,10 @@ they have their own way to be computed."
     (and (not (member property org-special-properties))
 	 (org-columns--spec-operator spec))))
 
+(defun org-columns--subtree-checkbox-summary-p (operator)
+  "Non-nil when OPERATOR counts the checkboxes of a whole subtree."
+  (member operator org-columns--checkbox-subtree-operators))
+
 (defun org-columns--compute-spec (spec &optional update-property-p)
   "Update tree according to SPEC.
 SPEC is a column format specification.  When optional argument
@@ -1594,6 +1608,7 @@ existing ones in properties drawers."
 	  (format-string (org-columns--spec-format-string spec))
 	  (collect-function (org-columns--collect-function operator))
 	  (summarize-function (org-columns--summarize-function operator))
+	  (subtree-checkboxes-p (org-columns--subtree-checkbox-summary-p operator))
 	  (stack nil))
       (org-with-wide-buffer
        ;; Walk the tree from the back and do the computations.
@@ -1618,8 +1633,11 @@ existing ones in properties drawers."
 			   (when update-property-p (org-columns--update-summary-property property current-value summary))
 			   summary)
 			  (t current-value))))
-	     (when (org-string-nw-p value)
-	       (push (cons level value) stack)))))))))
+	     (cond
+	      ((and summary subtree-checkboxes-p)
+	       (dolist (v (reverse child-values))
+		 (push (cons level v) stack)))
+	      ((org-string-nw-p value) (push (cons level value) stack))))))))))
 
 ;;;###autoload
 (defun org-columns-compute (property)
diff --git a/testing/lisp/test-org-colview.el b/testing/lisp/test-org-colview.el
index ffd0d5cf9..05c66b42a 100644
--- a/testing/lisp/test-org-colview.el
+++ b/testing/lisp/test-org-colview.el
@@ -1122,6 +1122,78 @@ https://list.orgmode.org/[email protected]/T/#u.";
       (list (get-char-property (point) 'org-columns-value-modified)
 	    (get-char-property (1+ (point)) 'org-columns-value-modified))))))
 
+(ert-deftest test-org-colview/columns-subtree-checkbox-statistics ()
+  "Test subtree checkbox summary types (Xs, Xs/, Xs%)."
+  ;; The tree has four leaf checkboxes (three checked, one unchecked).
+  ;; S1 has [1/2], S2 has [2/2].
+  (let ((tree "* H
+** S1
+*** S11
+:PROPERTIES:
+:A: [X]
+:END:
+*** S12
+:PROPERTIES:
+:A: [ ]
+:END:
+** S2
+*** S21
+:PROPERTIES:
+:A: [X]
+:END:
+*** S22
+:PROPERTIES:
+:A: [X]
+:END:"))
+    (cl-flet ((summary (format)
+                (org-test-with-temp-text tree
+                  (let ((org-columns-default-format format))
+                    (org-columns))
+                  (get-char-property (point) 'org-columns-value-modified))))
+      ;; Non-recursive (X, X/, X%) only cover children:
+      ;; H has two children: S1 ([1/2], incomplete) and S2 ([2/2], complete).
+      (should (equal "[1/2]" (summary "%A{X/}")))
+      (should (equal "[50%]" (summary "%A{X%}")))
+      (should (equal "[-]" (summary "%A{X}")))
+      ;; Subtree variants (Xs/, Xs%, Xs) count all checkboxes in the entire subtree (3 of 4 checked).
+      (should (equal "[3/4]" (summary "%A{Xs/}")))
+      (should (equal "[75%]" (summary "%A{Xs%}")))
+      (should (equal "[-]" (summary "%A{Xs}")))))
+  ;; When all checkboxes in subtree are checked, Xs summarizes to [X].
+  (should
+   (equal "[X]"
+          (org-test-with-temp-text
+              "* H
+** S1
+*** S11
+:PROPERTIES:
+:A: [X]
+:END:
+*** S12
+:PROPERTIES:
+:A: [X]
+:END:"
+            (let ((org-columns-default-format "%A{Xs}"))
+              (org-columns))
+            (get-char-property (point) 'org-columns-value-modified))))
+  ;; When a subheading has its own checkbox property and also has children,
+  ;; the children's values override the parent's value so it is not counted twice.
+  (should
+   (equal "[0/1]"
+          (org-test-with-temp-text
+              "* H
+** S1
+:PROPERTIES:
+:A: [X]
+:END:
+*** S11
+:PROPERTIES:
+:A: [ ]
+:END:"
+            (let ((org-columns-default-format "%A{Xs/}"))
+              (org-columns))
+            (get-char-property (point) 'org-columns-value-modified)))))
+
 (ert-deftest test-org-colview/columns-custom-summary-hierarchical ()
   "Test custom summary types on hierarchical headings."
   (should
-- 
2.56.0

Reply via email to