Any way to dynamically update `:title` in an Advanced Query?

At great expense to my sanity, I’ve written an advanced query that sums up its child blocks as timecard entries and displays them as a table and a total at the bottom.

Is it possible to update the title dynamically? In my case I’d like to show the timecard total when the block is collapsed.

My research tells me no, it’s not possible, but I’d like to ask here in case I’m missing something and to document the need for future generations.

#+BEGIN_QUERY
{:title "Timecard"
 :query [:find ?content
         :in $ ?current-block %
         :where
         [?child :block/parent ?current-block]
         [?child :block/content ?content]
         (time-content ?content)]
 :inputs [:current-block]
 :rules [
   [(time-content ?content)
    [(not= ?content "")]
    [(str "\\s*(\\d{1,2}):?(\\d{2})\\s+(\\d{1,2}):?(\\d{2})\\s*") ?pattern]
    [(re-pattern ?pattern) ?regex]
    [(re-find ?regex ?content)]]]
 :view (fn [results]
         (let [pattern (re-pattern "\\s*(\\d{1,2}):?(\\d{2})\\s+(\\d{1,2}):?(\\d{2})\\s*(.*)")
              h-to-m (fn [h]
                           (* h 60))
              format-duration (fn [mins]
                           (str (quot mins 60) "h " (mod mins 60) "m"))
              format-time (fn [h m]
                            (str (if (< h 10) (str "0" h) h) ":" 
                                 (if (< m 10) (str "0" m) m)))
               processed (for [content results]
                           (let [match (re-find pattern content)]
                             (when match
                               (let [[_ start-hour start-minute end-hour end-minute note] match
                                    start-h (read-string start-hour)
                                    start-m (read-string start-minute)
                                    end-h (read-string end-hour)
                                    end-m (read-string end-minute)
                                    start-total-mins (+ (h-to-m start-h) start-m)
                                    end-total-mins (+ (h-to-m end-h) end-m)
                                    duration-mins (- end-total-mins start-total-mins)
                                    formatted-span (str (format-time start-h start-m) " → " (format-time end-h end-m))]
                                 {:span formatted-span :duration duration-mins :note note}))))
               valid-entries (filter identity processed)
               total-mins (apply + (map :duration valid-entries))]
           [:div 
            [:table
             [:tbody
              (for [entry valid-entries]
                [:tr
                 [:td (:span entry)]
                 [:td (format-duration (:duration entry))]
                 [:td [:em {:style {:color "gray"}} (:note entry)]]])
              [:tr {:style { :font-weight "bold" :background-color "green"}}
               [:td {:style { :width "1%" :white-space "nowrap"}} "Total:"]
               [:td (format-duration total-mins) ]]]]]))}
#+END_QUERY

I really am hating logseq’s restricted clojure implementation. It’s poorly documented, virtually no library support. I can’t find a list of libraries I can lean on in these queries anywhere, so if you can assist please post it here.

Rant over, here is an update to the original query to account for 08 and 09 as a number, since it thinks it’s octal but an invalid octal and the query fails. js/parseInt would work fine, but not here.

#+BEGIN_QUERY
{:title "Timecard"
 :query [:find ?content
         :in $ ?current-block %
         :where
         [?child :block/parent ?current-block]
         [?child :block/content ?content]
         (time-content ?content)]
 :inputs [:current-block]
 :rules [
   [(time-content ?content)
    [(not= ?content "")]
    [(str "\\s*(\\d{1,2}):?(\\d{2})\\s+(\\d{1,2}):?(\\d{2})\\s*") ?pattern]
    [(re-pattern ?pattern) ?regex]
    [(re-find ?regex ?content)]]]
 :view (fn [results]
         (let [pattern (re-pattern "\\s*(\\d{1,2}):?(\\d{2})\\s+(\\d{1,2}):?(\\d{2})\\s*(.*)")
               safe-parse (fn [s]
                           (cond
                             (= s "08") 8
                             (= s "09") 9
                             :else (read-string s)))
               h-to-m (fn [h]
                        (* h 60))
               format-duration (fn [mins]
                                (str (quot mins 60) "h " (mod mins 60) "m"))
               format-decimal (fn [mins]
                                (let [hours (/ mins 60.0)
                                      tenths (int (* hours 10))
                                      remainder (- (* hours 10) tenths)
                                      rounded-tenths (if (>= remainder 0.5) (inc tenths) tenths)
                                      decimal-hours (/ rounded-tenths 10.0)
                                      whole (int decimal-hours)
                                      frac (- decimal-hours whole)]
                                  (str (if (= frac 0.0)
                                         (str whole ".0")
                                         (str decimal-hours)) "h")))
               format-time (fn [h m]
                            (str (if (< h 10) (str "0" h) h) ":" 
                                 (if (< m 10) (str "0" m) m)))
               processed (for [content results]
                           (let [match (re-find pattern content)]
                             (when match
                               (let [[_ start-hour start-minute end-hour end-minute note] match
                                    start-h (safe-parse start-hour)
                                    start-m (safe-parse start-minute)
                                    end-h (safe-parse end-hour)
                                    end-m (safe-parse end-minute)
                                    start-total-mins (+ (h-to-m start-h) start-m)
                                    end-total-mins (+ (h-to-m end-h) end-m)
                                    duration-mins (- end-total-mins start-total-mins)
                                    formatted-span (str (format-time start-h start-m) " → " (format-time end-h end-m))]
                                 {:span formatted-span :duration duration-mins :note note}))))
               valid-entries (filter identity processed)
               total-mins (apply + (map :duration valid-entries))]
           [:div 
            [:table
             [:tbody
              (for [entry valid-entries]
                [:tr
                 [:td (:span entry)]
                 [:td (format-duration (:duration entry))]
                 [:td (format-decimal (:duration entry))]
                 [:td [:em {:style {:color "gray"}} (:note entry)]]])
              [:tr {:style {:font-weight "bold" :background-color "green"}}
               [:td {:style {:white-space "nowrap"}} "Total:"]
               [:td (format-duration total-mins)]
               [:td (format-decimal total-mins)]
               [:td ""]]]]]))
}
#+END_QUERY

If sanity is the priority, should rather use custom Javascript to call the query from Logseq’s API and then render it however you like with library freedom. Granted this is a challenge of its own, but much healthier long-term. Because your future requirements will again stumble on similar limitations.