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
-