diff --git a/config/loader.go b/config/loader.go index d381c09..7548d85 100644 --- a/config/loader.go +++ b/config/loader.go @@ -148,21 +148,6 @@ func GetConfig() *Config { return appConfig } -// GetString is a convenience method to get a string value from config -func GetString(key string) string { - return viper.GetString(key) -} - -// GetBool is a convenience method to get a boolean value from config -func GetBool(key string) bool { - return viper.GetBool(key) -} - -// GetInt is a convenience method to get an integer value from config -func GetInt(key string) int { - return viper.GetInt(key) -} - // workflowFileData represents the YAML structure of workflow.yaml for read-modify-write. // kept in config package to avoid import cycle with plugin package. type workflowFileData struct { diff --git a/config/paths.go b/config/paths.go index f52aa83..f6797e2 100644 --- a/config/paths.go +++ b/config/paths.go @@ -397,12 +397,6 @@ func FindWorkflowFile() string { return files[0] } -// DefaultWorkflowFilePath returns the default path for creating a new workflow.yaml -// (in the project config dir, i.e. .doc/) -func DefaultWorkflowFilePath() string { - return filepath.Join(mustGetPathManager().ProjectConfigDir(), defaultWorkflowFilename) -} - // GetTemplateFile returns the path to the user's custom new.md template func GetTemplateFile() string { return mustGetPathManager().TemplateFile() diff --git a/controller/actions.go b/controller/actions.go index 59bed01..61c8e16 100644 --- a/controller/actions.go +++ b/controller/actions.go @@ -22,8 +22,6 @@ const ( // ActionID values for task navigation and manipulation (used by plugins). const ( - ActionOpenTask ActionID = "open_task" - ActionMoveTask ActionID = "move_task" ActionMoveTaskLeft ActionID = "move_task_left" ActionMoveTaskRight ActionID = "move_task_right" ActionNewTask ActionID = "new_task" diff --git a/controller/plugin.go b/controller/plugin.go index bb7f350..cfdde06 100644 --- a/controller/plugin.go +++ b/controller/plugin.go @@ -96,11 +96,6 @@ func (pc *PluginController) GetPluginName() string { return pc.pluginDef.Name } -// GetPluginDefinition returns the plugin definition -func (pc *PluginController) GetPluginDefinition() *plugin.TikiPlugin { - return pc.pluginDef -} - // HandleAction processes a plugin action func (pc *PluginController) HandleAction(actionID ActionID) bool { switch actionID { diff --git a/controller/task_detail.go b/controller/task_detail.go index 8eefbbd..1199f4b 100644 --- a/controller/task_detail.go +++ b/controller/task_detail.go @@ -389,65 +389,6 @@ func (tc *TaskController) handleCloneTask() bool { return true } -// SaveTaskDetails persists edited task fields in a single update -func (tc *TaskController) SaveTaskDetails(newTitle, newDescription string) bool { - if tc.currentTaskID == "" { - return false - } - - task := tc.taskStore.GetTask(tc.currentTaskID) - - // new task creation flow using draft (not yet persisted) - if task == nil && tc.draftTask != nil && tc.draftTask.ID == tc.currentTaskID { - title := strings.TrimSpace(newTitle) - if title == "" { - return false - } - - draft := tc.draftTask - draft.Title = title - draft.Description = newDescription - now := time.Now() - if draft.CreatedAt.IsZero() { - draft.CreatedAt = now - } - // Note: UpdatedAt will be computed after save based on file mtime - - setAuthorFromGit(draft, tc.taskStore) - - if err := tc.taskStore.CreateTask(draft); err != nil { - slog.Error("failed to create task from draft", "error", err) - // Don't clear draft on error - let user retry - return false - } - - tc.draftTask = nil - return true - } - - if task == nil { - return false - } - - updated := false - - if task.Title != newTitle { - task.Title = newTitle - updated = true - } - - if task.Description != newDescription { - task.Description = newDescription - updated = true - } - - if updated { - _ = tc.taskStore.UpdateTask(task) - } - - return true -} - // GetCurrentTask returns the task being viewed or edited. // Returns nil if no task is currently active. func (tc *TaskController) GetCurrentTask() *taskpkg.Task { diff --git a/model/plugin_config.go b/model/plugin_config.go index c3209f3..176e343 100644 --- a/model/plugin_config.go +++ b/model/plugin_config.go @@ -76,13 +76,6 @@ func (pc *PluginConfig) SetLaneLayout(columns []int) { } } -// GetLaneCount returns the number of lanes. -func (pc *PluginConfig) GetLaneCount() int { - pc.mu.RLock() - defer pc.mu.RUnlock() - return len(pc.laneColumns) -} - // GetSelectedLane returns the selected lane index. func (pc *PluginConfig) GetSelectedLane() int { pc.mu.RLock() diff --git a/plugin/definition.go b/plugin/definition.go index 6e83c0b..1866708 100644 --- a/plugin/definition.go +++ b/plugin/definition.go @@ -10,8 +10,6 @@ import ( type Plugin interface { GetName() string GetActivationKey() (tcell.Key, rune, tcell.ModMask) - GetForeground() tcell.Color - GetBackground() tcell.Color GetFilePath() string GetConfigIndex() int GetType() string @@ -40,14 +38,6 @@ func (p *BasePlugin) GetActivationKey() (tcell.Key, rune, tcell.ModMask) { return p.Key, p.Rune, p.Modifier } -func (p *BasePlugin) GetForeground() tcell.Color { - return p.Foreground -} - -func (p *BasePlugin) GetBackground() tcell.Color { - return p.Background -} - func (p *BasePlugin) GetFilePath() string { return p.FilePath } diff --git a/store/history.go b/store/history.go index 643d0b9..501a5eb 100644 --- a/store/history.go +++ b/store/history.go @@ -167,10 +167,6 @@ func (h *TaskHistory) Build() error { return nil } -func (h *TaskHistory) Transitions() map[string][]StatusChange { - return h.transitions -} - func (h *TaskHistory) Burndown() []BurndownPoint { if h.windowStart.IsZero() { return nil diff --git a/store/memory_store.go b/store/memory_store.go index f932d6c..108a259 100644 --- a/store/memory_store.go +++ b/store/memory_store.go @@ -104,37 +104,6 @@ func (s *InMemoryStore) UpdateTask(task *task.Task) error { return nil } -// UpdateStatus changes a task's status (with validation) -func (s *InMemoryStore) UpdateStatus(taskID string, newStatus task.Status) bool { - s.mu.Lock() - - taskID = normalizeTaskID(taskID) - task, exists := s.tasks[taskID] - if !exists { - s.mu.Unlock() - return false - } - - // validate transition (could add more rules here) - if !isValidTransition(task.Status, newStatus) { - s.mu.Unlock() - return false - } - - task.Status = newStatus - task.UpdatedAt = time.Now() - s.mu.Unlock() - s.notifyListeners() - return true -} - -// isValidTransition checks if a status transition is allowed -func isValidTransition(from, to task.Status) bool { - // for now, allow all transitions - // can add business rules here (e.g., can't go from done to backlog) - return from != to -} - // DeleteTask removes a task from the store func (s *InMemoryStore) DeleteTask(id string) { s.mu.Lock() @@ -155,20 +124,6 @@ func (s *InMemoryStore) GetAllTasks() []*task.Task { return tasks } -// GetTasksByStatus returns tasks filtered by status -func (s *InMemoryStore) GetTasksByStatus(status task.Status) []*task.Task { - s.mu.RLock() - defer s.mu.RUnlock() - - var tasks []*task.Task - for _, t := range s.tasks { - if t.Status == status { - tasks = append(tasks, t) - } - } - return tasks -} - // Search searches tasks with optional filter function (simplified in-memory version) func (s *InMemoryStore) Search(query string, filterFunc func(*task.Task) bool) []task.SearchResult { s.mu.RLock() diff --git a/store/store.go b/store/store.go index df5c4a7..5b4727c 100644 --- a/store/store.go +++ b/store/store.go @@ -25,18 +25,12 @@ type Store interface { // Returns error if save fails (IO error, ErrConflict). UpdateTask(task *task.Task) error - // UpdateStatus changes a task's status (with validation) - UpdateStatus(taskID string, newStatus task.Status) bool - // DeleteTask removes a task from the store DeleteTask(id string) // GetAllTasks returns all tasks GetAllTasks() []*task.Task - // GetTasksByStatus returns tasks filtered by status - GetTasksByStatus(status task.Status) []*task.Task - // Search searches tasks with optional filter function. // query: case-insensitive search term (searches task titles) // filterFunc: optional filter function to pre-filter tasks (nil = all tasks) diff --git a/store/tikistore/crud.go b/store/tikistore/crud.go index ceeb6ee..f23442c 100644 --- a/store/tikistore/crud.go +++ b/store/tikistore/crud.go @@ -80,39 +80,6 @@ func (s *TikiStore) UpdateTask(task *taskpkg.Task) error { return nil } -// UpdateStatus changes a task's status -func (s *TikiStore) UpdateStatus(taskID string, newStatus taskpkg.Status) bool { - s.mu.Lock() - - taskID = normalizeTaskID(taskID) - task, exists := s.tasks[taskID] - if !exists { - s.mu.Unlock() - return false - } - - oldStatus := task.Status // Capture old status for logging - - if task.Status == newStatus { - s.mu.Unlock() - slog.Debug("task status already matches new status, no update needed", "task_id", taskID, "status", newStatus) - return false - } - - task.Status = newStatus - if err := s.saveTask(task); err != nil { - slog.Error("failed to save task after status update", "task_id", taskID, "old_status", oldStatus, "new_status", newStatus, "error", err) - // Consider reverting task.Status if save fails - s.mu.Unlock() - return false - } - s.mu.Unlock() - slog.Info("task status updated", "task_id", taskID, "old_status", oldStatus, "new_status", newStatus) - // notify outside lock to prevent deadlock when listeners call back into store - s.notifyListeners() - return true -} - // DeleteTask removes a task and its file func (s *TikiStore) DeleteTask(id string) { s.mu.Lock() diff --git a/store/tikistore/query.go b/store/tikistore/query.go index a94d80e..8afac40 100644 --- a/store/tikistore/query.go +++ b/store/tikistore/query.go @@ -1,7 +1,6 @@ package tikistore import ( - "log/slog" "sort" "strings" @@ -21,22 +20,6 @@ func (s *TikiStore) GetAllTasks() []*taskpkg.Task { return tasks } -// GetTasksByStatus returns tasks filtered by status, sorted by priority then title -func (s *TikiStore) GetTasksByStatus(status taskpkg.Status) []*taskpkg.Task { - slog.Debug("retrieving tasks by status", "status", status) - s.mu.RLock() - defer s.mu.RUnlock() - - var tasks []*taskpkg.Task - for _, t := range s.tasks { - if t.Status == status { - tasks = append(tasks, t) - } - } - sortTasks(tasks) - return tasks -} - func matchesQuery(task *taskpkg.Task, queryLower string) bool { if task == nil || queryLower == "" { return false diff --git a/testutil/fixtures.go b/testutil/fixtures.go index 803340c..87e96cc 100644 --- a/testutil/fixtures.go +++ b/testutil/fixtures.go @@ -29,27 +29,3 @@ points: 1 return os.WriteFile(filepath, []byte(content), 0644) } - -// CreateBoardTasks creates sample tasks across all board panes -func CreateBoardTasks(dir string) error { - tasks := []struct { - id string - title string - status task.Status - taskType task.Type - }{ - {"TIKI-1", "Todo Task", task.StatusReady, task.TypeStory}, - {"TIKI-2", "In Progress Task", task.StatusInProgress, task.TypeStory}, - {"TIKI-3", "Review Task", task.StatusReview, task.TypeStory}, - {"TIKI-4", "Done Task", task.StatusDone, task.TypeStory}, - {"TIKI-5", "Another Todo", task.StatusReady, task.TypeBug}, - } - - for _, task := range tasks { - if err := CreateTestTask(dir, task.id, task.title, task.status, task.taskType); err != nil { - return fmt.Errorf("failed to create task %s: %w", task.id, err) - } - } - - return nil -} diff --git a/testutil/integration_helpers.go b/testutil/integration_helpers.go index 4dfd17c..4940eba 100644 --- a/testutil/integration_helpers.go +++ b/testutil/integration_helpers.go @@ -193,20 +193,6 @@ func (ta *TestApp) SendKey(key tcell.Key, ch rune, mod tcell.ModMask) { ta.Draw() } -// GetCell extracts the rune and style at a specific screen position -func (ta *TestApp) GetCell(x, y int) (rune, tcell.Style) { - contents, width, _ := ta.Screen.GetContents() - idx := y*width + x - if idx >= len(contents) { - return ' ', tcell.StyleDefault - } - cell := contents[idx] - if len(cell.Runes) > 0 { - return cell.Runes[0], cell.Style - } - return ' ', cell.Style -} - // GetTextAt extracts text from a screen region starting at (x, y) with given width func (ta *TestApp) GetTextAt(x, y, width int) string { contents, screenWidth, _ := ta.Screen.GetContents() diff --git a/util/sysinfo/sysinfo.go b/util/sysinfo/sysinfo.go index 8fa4e36..a11a417 100644 --- a/util/sysinfo/sysinfo.go +++ b/util/sysinfo/sysinfo.go @@ -89,20 +89,6 @@ func NewSystemInfo() *SystemInfo { return info } -// NewSystemInfoWithScreen collects all system information including actual terminal -// dimensions from a running screen. This requires an initialized tcell.Screen. -func NewSystemInfoWithScreen(screen tcell.Screen) *SystemInfo { - info := NewSystemInfo() - - // Get actual terminal dimensions - info.TerminalWidth, info.TerminalHeight = screen.Size() - - // Get verified color support from running screen - info.ColorSupport, info.ColorCount = getColorSupportFromScreen(screen) - - return info -} - // ToMap returns system information as a map for serialization. func (s *SystemInfo) ToMap() map[string]interface{} { return map[string]interface{}{ @@ -247,25 +233,6 @@ func getColorSupportFromTerminfo() (string, int) { } } -// getColorSupportFromScreen queries color capabilities from a running tcell screen. -// This is accurate but requires an initialized screen. -func getColorSupportFromScreen(screen tcell.Screen) (string, int) { - colors := screen.Colors() - - switch { - case colors >= 16777216: - return "truecolor", colors - case colors >= 256: - return "256-color", colors - case colors >= 16: - return "16-color", colors - case colors >= 2: - return "monochrome", colors - default: - return "unknown", colors - } -} - // getOSVersion attempts to detect OS version using platform-specific commands. // Returns "unknown" if detection fails (graceful fallback). func getOSVersion() string { diff --git a/view/gradient_caption_row.go b/view/gradient_caption_row.go index 17bc1fe..e7906f3 100644 --- a/view/gradient_caption_row.go +++ b/view/gradient_caption_row.go @@ -13,7 +13,6 @@ import ( type GradientCaptionRow struct { *tview.Box laneNames []string - bgColor tcell.Color // original background color from plugin gradient config.Gradient // computed gradient (for truecolor/256-color terminals) textColor tcell.Color } @@ -23,7 +22,6 @@ func NewGradientCaptionRow(laneNames []string, bgColor tcell.Color, textColor tc return &GradientCaptionRow{ Box: tview.NewBox(), laneNames: laneNames, - bgColor: bgColor, gradient: computeCaptionGradient(bgColor), textColor: textColor, } diff --git a/view/header/action_converter.go b/view/header/action_converter.go index 2396890..083e9d3 100644 --- a/view/header/action_converter.go +++ b/view/header/action_converter.go @@ -31,28 +31,6 @@ func convertHeaderActions(actions []model.HeaderAction) []controller.Action { return result } -// extractViewActions extracts view-specific actions from a registry, -// filtering out global actions and plugin actions (identified by "plugin:" prefix). -func extractViewActions(registry *controller.ActionRegistry, globalIDs map[controller.ActionID]bool) []controller.Action { - var viewActions []controller.Action - seen := make(map[controller.ActionID]bool) - - for _, action := range registry.GetHeaderActions() { - // skip if this is a global action or duplicate - if globalIDs[action.ID] || seen[action.ID] { - continue - } - // skip plugin actions (they're handled separately) - if strings.HasPrefix(string(action.ID), "plugin:") { - continue - } - seen[action.ID] = true - viewActions = append(viewActions, action) - } - - return viewActions -} - // extractViewActionsFromModel extracts view-specific actions from model.HeaderAction slice, // filtering out global actions and plugin actions. func extractViewActionsFromModel( diff --git a/view/header/context_help.go b/view/header/context_help.go index 6288a9e..ca19591 100644 --- a/view/header/context_help.go +++ b/view/header/context_help.go @@ -46,33 +46,6 @@ func NewContextHelpWidget() *ContextHelpWidget { } } -// SetActions updates the display with the given action registry (backward compatible) -// Returns the calculated visible width of the rendered content -func (chw *ContextHelpWidget) SetActions(registry *controller.ActionRegistry) int { - if registry == nil { - chw.SetText("") - chw.width = 0 - return 0 - } - - // Section 1: Global actions (always present) - globalRegistry := controller.DefaultGlobalActions() - var globalActions []controller.Action - globalIDs := make(map[controller.ActionID]bool) - for _, action := range globalRegistry.GetHeaderActions() { - globalActions = append(globalActions, action) - globalIDs[action.ID] = true - } - - // Section 2: Plugin "go to" actions (from centralized registry) - pluginActions := controller.GetPluginActions().GetHeaderActions() - - // Section 3: View-specific actions (exclude globals and plugin actions) - viewActions := extractViewActions(registry, globalIDs) - - return chw.renderActionsGrid(globalActions, pluginActions, viewActions) -} - // GetWidth returns the current calculated width of the content func (chw *ContextHelpWidget) GetWidth() int { return chw.width diff --git a/view/header/stats.go b/view/header/stats.go index 2631848..828970e 100644 --- a/view/header/stats.go +++ b/view/header/stats.go @@ -20,9 +20,6 @@ type StatCollector interface { // RemoveStat removes a stat by key. Returns true if stat existed. RemoveStat(key string) bool - - // GetStat retrieves current value for a stat. Returns empty string if not found. - GetStat(key string) string } // statEntry represents a single stat in the widget @@ -106,17 +103,6 @@ func (sw *StatsWidget) RemoveStat(key string) bool { return true } -// GetStat retrieves current value for a stat. Returns empty string if not found. -func (sw *StatsWidget) GetStat(key string) string { - sw.mu.RLock() - defer sw.mu.RUnlock() - - if entry, exists := sw.stats[key]; exists { - return entry.value - } - return "" -} - // GetKeys returns all current stat keys func (sw *StatsWidget) GetKeys() []string { sw.mu.RLock() diff --git a/view/root_layout.go b/view/root_layout.go index c3ce15b..c8f9570 100644 --- a/view/root_layout.go +++ b/view/root_layout.go @@ -226,14 +226,6 @@ func (rl *RootLayout) GetContentView() controller.View { return rl.contentView } -// RecomputeHeaderVisibility recomputes header visibility based on current view state. -// Call this when fullscreen state changes on a view not managed by RootLayout. -func (rl *RootLayout) RecomputeHeaderVisibility() { - if rl.contentView != nil { - rl.recomputeHeaderVisibility(rl.contentView) - } -} - // OnFocus delegates to the content view func (rl *RootLayout) OnFocus() { if rl.contentView != nil { diff --git a/view/taskdetail/base.go b/view/taskdetail/base.go index 94cc9c1..8196883 100644 --- a/view/taskdetail/base.go +++ b/view/taskdetail/base.go @@ -53,11 +53,6 @@ func (b *Base) GetPrimitive() tview.Primitive { return b.root } -// GetTaskID returns the task being displayed -func (b *Base) GetTaskID() string { - return b.taskID -} - // SetFallbackTask sets a task to render when it does not yet exist in the store (draft mode) func (b *Base) SetFallbackTask(task *taskpkg.Task) { b.fallbackTask = task diff --git a/view/taskdetail/task_edit_view.go b/view/taskdetail/task_edit_view.go index e27797e..3627954 100644 --- a/view/taskdetail/task_edit_view.go +++ b/view/taskdetail/task_edit_view.go @@ -33,7 +33,6 @@ type TaskEditView struct { titleEditing bool descTextArea *tview.TextArea descEditing bool - isEditing bool statusSelectList *component.EditSelectList typeSelectList *component.EditSelectList priorityInput *component.IntEditSelect @@ -72,7 +71,6 @@ func NewTaskEditView(taskStore store.Store, taskID string, renderer renderer.Mar focusedField: model.EditFieldTitle, titleEditing: true, descEditing: true, - isEditing: true, } ev.build() @@ -422,16 +420,6 @@ func (ev *TaskEditView) ExitFullscreen() { } } -// SetEditing sets the editing state -func (ev *TaskEditView) SetEditing(editing bool) { - ev.isEditing = editing -} - -// IsEditing returns whether the view is currently in edit mode -func (ev *TaskEditView) IsEditing() bool { - return ev.isEditing -} - // ShowTitleEditor displays the title input field func (ev *TaskEditView) ShowTitleEditor() tview.Primitive { task := ev.GetTask() diff --git a/view/tiki_plugin_view.go b/view/tiki_plugin_view.go index 4720176..9a89b58 100644 --- a/view/tiki_plugin_view.go +++ b/view/tiki_plugin_view.go @@ -206,31 +206,6 @@ func (pv *PluginView) OnBlur() { pv.pluginConfig.RemoveSelectionListener(pv.selectionListenerID) } -// GetSelectedID returns the selected task ID -func (pv *PluginView) GetSelectedID() string { - lane := pv.pluginConfig.GetSelectedLane() - tasks := pv.getLaneTasks(lane) - idx := pv.pluginConfig.GetSelectedIndexForLane(lane) - if idx >= 0 && idx < len(tasks) { - return tasks[idx].ID - } - return "" -} - -// SetSelectedID sets the selection to a task -func (pv *PluginView) SetSelectedID(id string) { - for lane := range pv.pluginDef.Lanes { - tasks := pv.getLaneTasks(lane) - for i, t := range tasks { - if t.ID == id { - pv.pluginConfig.SetSelectedLane(lane) - pv.pluginConfig.SetSelectedIndexForLane(lane, i) - return - } - } - } -} - // ShowSearch displays the search box and returns the primitive to focus func (pv *PluginView) ShowSearch() tview.Primitive { if pv.searchHelper.IsVisible() {