Add syncing

This commit is contained in:
Martin Pander
2026-02-03 16:04:47 +01:00
parent 2e33893e29
commit 44ddbc0f47
12 changed files with 480 additions and 72 deletions

View File

@ -21,6 +21,7 @@ type TimePage struct {
data timewarrior.Intervals
shouldSelectActive bool
pendingSyncAction string // "start", "stop", or "" (empty means no pending action)
selectedTimespan string
subpage common.Component
@ -163,8 +164,20 @@ func (p *TimePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case intervalsMsg:
p.data = timewarrior.Intervals(msg)
p.populateTable(p.data)
// If we have a pending sync action (from continuing an interval),
// execute it now that intervals are refreshed
if p.pendingSyncAction != "" {
action := p.pendingSyncAction
p.pendingSyncAction = ""
cmds = append(cmds, p.syncActiveIntervalAfterRefresh(action))
}
case RefreshIntervalsMsg:
cmds = append(cmds, p.getIntervals())
cmds = append(cmds, doTick())
case BackMsg:
// Restart tick loop when returning from subpage
cmds = append(cmds, doTick())
case tickMsg:
cmds = append(cmds, p.getIntervals())
cmds = append(cmds, doTick())
@ -182,13 +195,34 @@ func (p *TimePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
row := p.intervals.SelectedRow()
if row != nil {
interval := (*timewarrior.Interval)(row)
if interval.IsActive() {
p.common.TimeW.StopTracking()
} else {
p.common.TimeW.ContinueInterval(interval.ID)
p.shouldSelectActive = true
// Validate interval before proceeding
if interval.IsGap {
slog.Debug("Cannot start/stop gap interval")
return p, nil
}
return p, tea.Batch(p.getIntervals(), doTick())
if interval.IsActive() {
// Stop tracking
p.common.TimeW.StopTracking()
// Sync: stop corresponding Taskwarrior task immediately (interval has UUID)
common.SyncIntervalToTask(interval, p.common.TW, "stop")
} else {
// Continue tracking - creates a NEW interval
slog.Info("Continuing interval for task sync",
"intervalID", interval.ID,
"hasUUID", timewarrior.ExtractUUID(interval.Tags) != "",
"uuid", timewarrior.ExtractUUID(interval.Tags))
p.common.TimeW.ContinueInterval(interval.ID)
common.SyncIntervalToTask(interval, p.common.TW, "start")
p.shouldSelectActive = true
// Set pending sync action instead of syncing immediately
// This ensures we sync AFTER intervals are refreshed
p.pendingSyncAction = "start"
}
cmds = append(cmds, p.getIntervals())
cmds = append(cmds, doTick())
return p, tea.Batch(cmds...)
}
case key.Matches(msg, p.common.Keymap.Delete):
row := p.intervals.SelectedRow()
@ -351,7 +385,8 @@ func (p *TimePage) populateTable(intervals timewarrior.Intervals) {
{Title: "Start", Name: startField, Width: startEndWidth},
{Title: "End", Name: endField, Width: startEndWidth},
{Title: "Duration", Name: "duration", Width: 10},
{Title: "Tags", Name: "tags", Width: 0}, // flexible width
{Title: "Project", Name: "project", Width: 0}, // flexible width
{Title: "Tags", Name: "tags", Width: 0}, // flexible width
}
// Calculate table height: total height - header (1 line) - blank line (1) - safety (1)
@ -419,3 +454,34 @@ func (p *TimePage) getIntervals() tea.Cmd {
return intervalsMsg(intervals)
}
}
// syncActiveInterval creates a command that syncs the currently active interval to Taskwarrior
func (p *TimePage) syncActiveInterval(action string) tea.Cmd {
return func() tea.Msg {
// Get the currently active interval
activeInterval := p.common.TimeW.GetActive()
if activeInterval != nil {
common.SyncIntervalToTask(activeInterval, p.common.TW, action)
}
return nil
}
}
// syncActiveIntervalAfterRefresh is called AFTER intervals have been refreshed
// to ensure we're working with current data
func (p *TimePage) syncActiveIntervalAfterRefresh(action string) tea.Cmd {
return func() tea.Msg {
// At this point, intervals have been refreshed, so GetActive() will work
activeInterval := p.common.TimeW.GetActive()
if activeInterval != nil {
slog.Info("Syncing active interval to task after refresh",
"action", action,
"intervalID", activeInterval.ID,
"hasUUID", timewarrior.ExtractUUID(activeInterval.Tags) != "")
common.SyncIntervalToTask(activeInterval, p.common.TW, action)
} else {
slog.Warn("No active interval found after refresh, cannot sync to task")
}
return nil
}
}