Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions internal/tui/core/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type panel = tuistate.Panel
const (
panelTranscript panel = tuistate.PanelTranscript
panelActivity panel = tuistate.PanelActivity
panelTodo panel = tuistate.PanelTodo
panelInput panel = tuistate.PanelInput
)

Expand Down Expand Up @@ -84,6 +85,7 @@ type appComponents struct {
progress progress.Model
transcript viewport.Model
activity viewport.Model
todo viewport.Model
input textarea.Model
markdownRenderer markdownContentRenderer
}
Expand All @@ -101,6 +103,11 @@ type appRuntimeState struct {
pasteMode bool
activeMessages []providertypes.Message
activities []tuistate.ActivityEntry
todoItems []todoViewItem
todoFilter todoFilter
todoSelectedIndex int
todoPanelVisible bool
todoCollapsed bool
fileCandidates []string
modelRefreshID string
focus panel
Expand All @@ -111,6 +118,10 @@ type appRuntimeState struct {
pendingPermission *permissionPromptState
pendingImageAttachments []pendingImageAttachment
providerAddForm *providerAddFormState
layoutCached bool
cachedWidth int
cachedHeight int
viewDirty bool
}

type pendingImageAttachment struct {
Expand Down Expand Up @@ -265,13 +276,18 @@ func newApp(container tuibootstrap.Container) (App, error) {
progress: progressBar,
transcript: viewport.New(0, 0),
activity: viewport.New(0, 0),
todo: viewport.New(0, 0),
input: input,
markdownRenderer: markdownRenderer,
},
appRuntimeState: appRuntimeState{
codeCopyBlocks: make(map[int]string),
nowFn: time.Now,
focus: panelInput,
todoFilter: todoFilterAll,
layoutCached: true,
cachedWidth: 128,
cachedHeight: 40,
},
width: 128,
height: 40,
Expand Down Expand Up @@ -299,11 +315,16 @@ func newApp(container tuibootstrap.Container) (App, error) {
return app, nil
}

type tickMsg time.Time

func (a App) Init() tea.Cmd {
cmds := []tea.Cmd{
ListenForRuntimeEvent(a.runtime.Events()),
textarea.Blink,
a.spinner.Tick,
tea.Tick(100*time.Millisecond, func(t time.Time) tea.Msg {
return tickMsg(t)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium – perpetual no-op tick loop

A new tea.Tick(100ms) is started here, and handleTickMsg re-schedules it forever, but the tick handler does not apply any state changes. This adds constant wakeups/renders with no functional effect.

If this is meant to drive deferred redraw, gate it behind an actual dirty flag and stop ticking when idle.

}),
}
if cmd := runModelCatalogRefresh(a.providerSvc, a.modelRefreshID); cmd != nil {
cmds = append(cmds, cmd)
Expand Down
5 changes: 5 additions & 0 deletions internal/tui/core/app/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const (

activityTitle = "Activity"
activitySubtitle = "Latest execution events"
todoTitle = "Todos"

draftSessionTitle = "Draft"
emptyConversationText = "No conversation yet.\nAsk NeoCode to inspect or change code, or type /help to browse local commands."
Expand All @@ -85,6 +86,9 @@ const (
statusChooseProvider = "Choose a provider"
statusChooseModel = "Choose a model"
statusChooseSession = "Choose a session"
statusTodoFilterChanged = "Todo filter updated"
statusTodoCollapsed = "Todo list collapsed"
statusTodoExpanded = "Todo list expanded"
statusChooseHelp = "Choose a slash command"
statusBrowseFile = "Browse workspace files"
statusPermissionRequired = "Permission required: choose a decision and press Enter"
Expand All @@ -94,6 +98,7 @@ const (
focusLabelSessions = "Sessions"
focusLabelTranscript = "Transcript"
focusLabelActivity = "Activity"
focusLabelTodo = "Todo"
focusLabelComposer = "Composer"

maxActivityEntries = 64
Expand Down
13 changes: 12 additions & 1 deletion internal/tui/core/app/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,21 @@ func TestBuiltinSlashCommands(t *testing.T) {
}

found := false
foundTodo := false
for _, cmd := range builtinSlashCommands {
if cmd.Usage == slashUsageHelp {
found = true
break
}
if strings.HasPrefix(cmd.Usage, "/todo") {
foundTodo = true
}
}
if !found {
t.Error("expected to find /help command")
}
if foundTodo {
t.Error("did not expect /todo command in builtin slash commands")
}
}

func TestNewSelectionPicker(t *testing.T) {
Expand Down Expand Up @@ -75,6 +81,8 @@ func TestStatusConstants(t *testing.T) {
{"statusCompacting", statusCompacting},
{"statusChooseProvider", statusChooseProvider},
{"statusChooseModel", statusChooseModel},
{"statusTodoCollapsed", statusTodoCollapsed},
{"statusTodoExpanded", statusTodoExpanded},
{"statusChooseHelp", statusChooseHelp},
{"statusBrowseFile", statusBrowseFile},
}
Expand All @@ -98,6 +106,9 @@ func TestFocusLabels(t *testing.T) {
if focusLabelActivity == "" {
t.Error("focusLabelActivity should not be empty")
}
if focusLabelTodo == "" {
t.Error("focusLabelTodo should not be empty")
}
if focusLabelComposer == "" {
t.Error("focusLabelComposer should not be empty")
}
Expand Down
6 changes: 6 additions & 0 deletions internal/tui/core/app/permission_prompt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ func TestRenderPermissionPrompt(t *testing.T) {
},
Selected: 0,
},
layoutCached: true,
cachedWidth: 128,
cachedHeight: 40,
},
}
rendered := app.renderPermissionPrompt()
Expand Down Expand Up @@ -163,6 +166,9 @@ func TestRenderPromptWithPendingPermission(t *testing.T) {
Request: agentruntime.PermissionRequestPayload{ToolName: "bash", Target: "git status"},
Selected: 0,
},
layoutCached: true,
cachedWidth: 128,
cachedHeight: 40,
},
}
rendered := app.renderPrompt(80)
Expand Down
Loading
Loading