From cbaec2fd73667044a8e2b7a3c74b0c4b79f1055a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Nov 2025 22:26:27 +0000 Subject: [PATCH 1/4] Initial plan From 38f82cb69f470af68ae3bb55d12ed8ec080677a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Nov 2025 22:34:56 +0000 Subject: [PATCH 2/4] Add split overlapping records feature for issue #499 Co-authored-by: mcbloch <12089026+mcbloch@users.noreply.github.com> --- timetagger/app/dialogs.py | 67 +++++++++++++++++++++++++++++++++++++++ timetagger/app/utils.py | 1 + 2 files changed, 68 insertions(+) diff --git a/timetagger/app/dialogs.py b/timetagger/app/dialogs.py index 76604ec..0500b4d 100644 --- a/timetagger/app/dialogs.py +++ b/timetagger/app/dialogs.py @@ -1892,6 +1892,13 @@ def submit(self): # Prevent multiple timers at once if self._record.t1 == self._record.t2: self._stop_all_running_records(self._record.t1) + # Handle overlapping records if enabled (only for non-running records) + if ( + window.simplesettings.get("split_overlapping_records") + and self._record.t1 != self._record.t2 + and self._lmode in ("new", "edit") + ): + self._split_overlapping_records(self._record) # Apply window.store.records.put(self._record) super().submit(self._record) @@ -1905,6 +1912,52 @@ def submit(self): elif self._lmode == "stop": self._canvas.pomodoro_dialog.stop() + def _split_overlapping_records(self, new_record): + """Split existing records that overlap with the new record.""" + t1, t2 = new_record.t1, new_record.t2 + + # Get overlapping records + overlapping_records = window.store.records.get_records(t1, t2) + + for key in overlapping_records.keys(): + existing = overlapping_records[key] + # Skip the record being edited + if existing.key == new_record.key: + continue + # Skip running records + if existing.t1 == existing.t2: + continue + + # Check overlap type: + # - existing.t1 < t1 and existing.t2 > t2: new record is fully inside existing + # - existing.t1 >= t1 and existing.t2 <= t2: existing is fully inside new (do nothing) + # - existing.t1 < t1 and existing.t2 > t1 and existing.t2 <= t2: partial overlap at start + # - existing.t1 >= t1 and existing.t1 < t2 and existing.t2 > t2: partial overlap at end + + if existing.t1 < t1 and existing.t2 > t2: + # New record is fully inside existing - split into two records + # First part: existing.t1 to new_record.t1 + # Second part: new_record.t2 to existing.t2 + first_part = existing.copy() + first_part.t2 = t1 + + second_part = window.store.records.create(t2, existing.t2, existing.ds) + + window.store.records.put(first_part) + window.store.records.put(second_part) + + elif existing.t1 < t1 and existing.t2 > t1 and existing.t2 <= t2: + # Partial overlap at start - trim end of existing record + trimmed = existing.copy() + trimmed.t2 = t1 + window.store.records.put(trimmed) + + elif existing.t1 >= t1 and existing.t1 < t2 and existing.t2 > t2: + # Partial overlap at end - trim start of existing record + trimmed = existing.copy() + trimmed.t1 = t2 + window.store.records.put(trimmed) + def resume_record(self): """Start a new record with the same description.""" # The resume button should only be visible for non-running records, but @@ -3960,6 +4013,9 @@ def open(self, callback=None): +
@@ -4009,6 +4065,7 @@ def open(self, callback=None): self._repr_form, _, # Misc header self._stopwatch_label, + self._split_overlapping_label, _, # hr _, # Section: per device _, # Appearance header @@ -4075,6 +4132,12 @@ def open(self, callback=None): self._stopwatch_check.checked = show_stopwatch self._stopwatch_check.onchange = self._on_stopwatch_check + # Split overlapping records + split_overlapping = window.simplesettings.get("split_overlapping_records") + self._split_overlapping_check = self._split_overlapping_label.children[0] + self._split_overlapping_check.checked = split_overlapping + self._split_overlapping_check.onchange = self._on_split_overlapping_check + # Device settings # Dark mode @@ -4165,6 +4228,10 @@ def _on_stopwatch_check(self): show_stopwatch = bool(self._stopwatch_check.checked) window.simplesettings.set("show_stopwatch", show_stopwatch) + def _on_split_overlapping_check(self): + split_overlapping = bool(self._split_overlapping_check.checked) + window.simplesettings.set("split_overlapping_records", split_overlapping) + class GuideDialog(BaseDialog): """Dialog to have quick access to the guide.""" diff --git a/timetagger/app/utils.py b/timetagger/app/utils.py index 4139d01..38ec68e 100644 --- a/timetagger/app/utils.py +++ b/timetagger/app/utils.py @@ -629,6 +629,7 @@ def __init__(self): "today_snap_offset": "", "today_end_offset": "", "show_stopwatch": True, + "split_overlapping_records": False, } # The data store for synced source self._store = None From 264d551d0d6f88793319b2114ac6bfa3cb0fc38a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Nov 2025 22:37:00 +0000 Subject: [PATCH 3/4] Address code review feedback: use .items() and add explicit no-op case Co-authored-by: mcbloch <12089026+mcbloch@users.noreply.github.com> --- timetagger/app/dialogs.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/timetagger/app/dialogs.py b/timetagger/app/dialogs.py index 0500b4d..dad35d6 100644 --- a/timetagger/app/dialogs.py +++ b/timetagger/app/dialogs.py @@ -1919,8 +1919,7 @@ def _split_overlapping_records(self, new_record): # Get overlapping records overlapping_records = window.store.records.get_records(t1, t2) - for key in overlapping_records.keys(): - existing = overlapping_records[key] + for key, existing in overlapping_records.items(): # Skip the record being edited if existing.key == new_record.key: continue @@ -1946,6 +1945,11 @@ def _split_overlapping_records(self, new_record): window.store.records.put(first_part) window.store.records.put(second_part) + elif existing.t1 >= t1 and existing.t2 <= t2: + # Existing record is fully inside the new record - no action needed + # The user is placing a record that completely covers this one + pass + elif existing.t1 < t1 and existing.t2 > t1 and existing.t2 <= t2: # Partial overlap at start - trim end of existing record trimmed = existing.copy() From 9ce3bc57105cb40e91300b9277a3b30d155c4a5c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Nov 2025 23:24:22 +0000 Subject: [PATCH 4/4] Fix Misc settings layout to display checkboxes on separate lines Co-authored-by: mcbloch <12089026+mcbloch@users.noreply.github.com> --- timetagger/app/dialogs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/timetagger/app/dialogs.py b/timetagger/app/dialogs.py index dad35d6..b56f55c 100644 --- a/timetagger/app/dialogs.py +++ b/timetagger/app/dialogs.py @@ -4014,10 +4014,10 @@ def open(self, callback=None):

\uf085  Misc

-