55"""
66
77import html as _html
8+ import os
89import sqlite3
10+ import subprocess
11+ import threading
912import uuid
1013from datetime import datetime , timedelta , timezone
1114
@@ -214,7 +217,7 @@ def delete_task(self, task_id):
214217 QDialogButtonBox ,
215218)
216219from PyQt6 .QtGui import QIcon , QAction , QPixmap , QPainter , QColor , QFont
217- from PyQt6 .QtCore import QEvent , QSettings , Qt , QTimer , QPoint
220+ from PyQt6 .QtCore import QEvent , QSettings , Qt , QTimer , QPoint , pyqtSignal
218221
219222
220223def create_tray_icon_pixmap (overdue_count = 0 ):
@@ -838,6 +841,8 @@ def _context_menu(self, pos):
838841class FullWindow (QMainWindow ):
839842 """Full task manager window with tabs, search, sort, and suggested view."""
840843
844+ _bridge_done = pyqtSignal (str )
845+
841846 # Sort modes cycle: priority → due → created → priority ...
842847 _SORT_MODES = ("priority" , "due" , "created" )
843848 _SORT_LABELS = {
@@ -915,8 +920,8 @@ def __init__(self, db, parent=None):
915920 add_action = QAction ("+ Add Task" , self )
916921 add_action .triggered .connect (self ._add_task )
917922 toolbar .addAction (add_action )
918- refresh_action = QAction ("Refresh" , self )
919- refresh_action .triggered .connect (self .refresh )
923+ refresh_action = QAction ("Refresh + Sync " , self )
924+ refresh_action .triggered .connect (self ._refresh_and_sync )
920925 toolbar .addAction (refresh_action )
921926 toolbar .addSeparator ()
922927
@@ -940,6 +945,9 @@ def __init__(self, db, parent=None):
940945 self .status = QStatusBar ()
941946 self .setStatusBar (self .status )
942947
948+ # Bridge sync signal (thread-safe → main thread)
949+ self ._bridge_done .connect (lambda msg : self .status .showMessage (msg , 5000 ))
950+
943951 # Auto-refresh every 30s
944952 self ._refresh_timer = QTimer (self )
945953 self ._refresh_timer .timeout .connect (self .refresh )
@@ -955,6 +963,49 @@ def __init__(self, db, parent=None):
955963 def _run_purge (self ):
956964 self ._last_purged = self .db .purge_old_done (days = 30 )
957965
966+ # ── Bridge sync ────────────────────────────────────────────────────
967+
968+ _BRIDGE_DIR = os .path .expanduser ("~/.claude/memory/bridge" )
969+
970+ def _refresh_and_sync (self ):
971+ """Refresh task list then sync memory bridge to GitHub."""
972+ self .refresh ()
973+ self ._sync_bridge ()
974+
975+ def _sync_bridge (self ):
976+ """Git add/commit/push the memory bridge in a background thread."""
977+ if not os .path .isdir (self ._BRIDGE_DIR ):
978+ self .status .showMessage ("Bridge dir not found" , 3000 )
979+ return
980+
981+ self .status .showMessage ("Syncing bridge to GitHub..." )
982+
983+ def _do_sync ():
984+ try :
985+ subprocess .run (
986+ ["git" , "add" , "-A" ],
987+ cwd = self ._BRIDGE_DIR , capture_output = True , timeout = 10 ,
988+ )
989+ result = subprocess .run (
990+ ["git" , "commit" , "-m" , "bridge: sync from task tray" ],
991+ cwd = self ._BRIDGE_DIR , capture_output = True , timeout = 10 ,
992+ )
993+ if result .returncode == 0 :
994+ subprocess .run (
995+ ["git" , "push" ],
996+ cwd = self ._BRIDGE_DIR , capture_output = True , timeout = 30 ,
997+ )
998+ return "Bridge synced to GitHub"
999+ return "Bridge: nothing to sync"
1000+ except Exception as exc :
1001+ return f"Bridge sync error: { exc } "
1002+
1003+ def _run ():
1004+ msg = _do_sync ()
1005+ self ._bridge_done .emit (msg )
1006+
1007+ threading .Thread (target = _run , daemon = True ).start ()
1008+
9581009 def _sort_tasks (self , tasks ):
9591010 """Sort tasks by current sort mode."""
9601011 mode = self ._sort_mode
0 commit comments