summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-07-18 13:11:35 -0400
committerDax Raad <[email protected]>2025-07-18 13:40:07 -0400
commitc952e9ae3d74dcdda2a4fbdfef19b42c49096026 (patch)
tree7ebba41e220815a29d3a768582ade948ad3ec45a /packages
parent64702430950f3f842daca6e06e5c2f25fd456e0c (diff)
downloadopencode-c952e9ae3d74dcdda2a4fbdfef19b42c49096026.tar.gz
opencode-c952e9ae3d74dcdda2a4fbdfef19b42c49096026.zip
message rendering performance improvements
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/index.ts5
-rw-r--r--packages/opencode/src/trace/index.ts53
-rw-r--r--packages/opencode/src/util/log.ts19
-rw-r--r--packages/tui/internal/app/app.go4
-rw-r--r--packages/tui/internal/components/chat/messages.go507
5 files changed, 329 insertions, 259 deletions
diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts
index df7f1aa88..73ff26c6f 100644
--- a/packages/opencode/src/index.ts
+++ b/packages/opencode/src/index.ts
@@ -17,6 +17,9 @@ import { DebugCommand } from "./cli/cmd/debug"
import { StatsCommand } from "./cli/cmd/stats"
import { McpCommand } from "./cli/cmd/mcp"
import { InstallGithubCommand } from "./cli/cmd/install-github"
+import { Trace } from "./trace"
+
+Trace.init()
const cancel = new AbortController()
@@ -42,7 +45,7 @@ const cli = yargs(hideBin(process.argv))
type: "boolean",
})
.middleware(async () => {
- await Log.init({ print: process.argv.includes("--print-logs") })
+ await Log.init({ print: process.argv.includes("--print-logs"), dev: Installation.isDev() })
try {
const { Config } = await import("./config/config")
diff --git a/packages/opencode/src/trace/index.ts b/packages/opencode/src/trace/index.ts
new file mode 100644
index 000000000..8dba93d50
--- /dev/null
+++ b/packages/opencode/src/trace/index.ts
@@ -0,0 +1,53 @@
+import { Global } from "../global"
+import { Installation } from "../installation"
+import path from "path"
+
+export namespace Trace {
+ export function init() {
+ if (!Installation.isDev()) return
+ const writer = Bun.file(path.join(Global.Path.data, "log", "fetch.log")).writer()
+
+ const originalFetch = globalThis.fetch
+ // @ts-expect-error
+ globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url
+ const method = init?.method || "GET"
+
+ const urlObj = new URL(url)
+
+ writer.write(`\n${method} ${urlObj.pathname}${urlObj.search} HTTP/1.1\n`)
+ writer.write(`Host: ${urlObj.host}\n`)
+
+ if (init?.headers) {
+ if (init.headers instanceof Headers) {
+ init.headers.forEach((value, key) => {
+ writer.write(`${key}: ${value}\n`)
+ })
+ } else {
+ for (const [key, value] of Object.entries(init.headers)) {
+ writer.write(`${key}: ${value}\n`)
+ }
+ }
+ }
+
+ if (init?.body) {
+ writer.write(`\n${init.body}`)
+ }
+ writer.flush()
+ const response = await originalFetch(input, init)
+ const clonedResponse = response.clone()
+ writer.write(`\nHTTP/1.1 ${response.status} ${response.statusText}\n`)
+ response.headers.forEach((value, key) => {
+ writer.write(`${key}: ${value}\n`)
+ })
+ if (clonedResponse.body) {
+ clonedResponse.text().then(async (x) => {
+ writer.write(`\n${x}\n`)
+ })
+ }
+ writer.flush()
+
+ return response
+ }
+ }
+}
diff --git a/packages/opencode/src/util/log.ts b/packages/opencode/src/util/log.ts
index 2c9c91938..c3cb04d29 100644
--- a/packages/opencode/src/util/log.ts
+++ b/packages/opencode/src/util/log.ts
@@ -50,6 +50,7 @@ export namespace Log {
export interface Options {
print: boolean
+ dev?: boolean
level?: Level
}
@@ -63,7 +64,10 @@ export namespace Log {
await fs.mkdir(dir, { recursive: true })
cleanup(dir)
if (options.print) return
- logpath = path.join(dir, new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log")
+ logpath = path.join(
+ dir,
+ options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
+ )
const logfile = Bun.file(logpath)
await fs.truncate(logpath).catch(() => {})
const writer = logfile.writer()
@@ -75,15 +79,16 @@ export namespace Log {
}
async function cleanup(dir: string) {
- const entries = await fs.readdir(dir, { withFileTypes: true })
- const files = entries
- .filter((entry) => entry.isFile() && entry.name.endsWith(".log"))
- .map((entry) => path.join(dir, entry.name))
-
+ const glob = new Bun.Glob("????-??-??T??????.log")
+ const files = await Array.fromAsync(
+ glob.scan({
+ cwd: dir,
+ absolute: true,
+ }),
+ )
if (files.length <= 5) return
const filesToDelete = files.slice(0, -10)
-
await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {})))
}
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index 011d5a89a..57d9f98a3 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -68,9 +68,6 @@ type SendMsg struct {
type SetEditorContentMsg struct {
Text string
}
-type OptimisticMessageAddedMsg struct {
- Message opencode.MessageUnion
-}
type FileRenderedMsg struct {
FilePath string
}
@@ -508,7 +505,6 @@ func (a *App) SendChatMessage(
}
a.Messages = append(a.Messages, Message{Info: message, Parts: parts})
- cmds = append(cmds, util.CmdHandler(OptimisticMessageAddedMsg{Message: message}))
cmds = append(cmds, func() tea.Msg {
partsParam := []opencode.SessionChatParamsPartUnion{}
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index 9c2dd7e81..bf0c4d8f6 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -36,13 +36,14 @@ type messagesComponent struct {
header string
viewport viewport.Model
cache *PartCache
- rendering bool
+ loading bool
showToolDetails bool
+ rendering bool
+ dirty bool
tail bool
partCount int
lineCount int
}
-type renderFinishedMsg struct{}
type ToggleToolDetailsMsg struct{}
@@ -62,34 +63,24 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.width = effectiveWidth
m.height = msg.Height - 7
m.viewport.SetWidth(m.width)
- m.header = m.renderHeader()
+ m.loading = true
return m, m.Reload()
case app.SendMsg:
m.viewport.GotoBottom()
m.tail = true
return m, nil
- case app.OptimisticMessageAddedMsg:
- m.tail = true
- m.rendering = true
- return m, m.Reload()
case dialog.ThemeSelectedMsg:
m.cache.Clear()
- m.rendering = true
+ m.loading = true
return m, m.Reload()
case ToggleToolDetailsMsg:
m.showToolDetails = !m.showToolDetails
- m.rendering = true
return m, m.Reload()
case app.SessionLoadedMsg, app.SessionClearedMsg:
m.cache.Clear()
m.tail = true
- m.rendering = true
+ m.loading = true
return m, m.Reload()
- case renderFinishedMsg:
- m.rendering = false
- if m.tail {
- m.viewport.GotoBottom()
- }
case opencode.EventListResponseEventSessionUpdated:
if msg.Properties.Info.ID == m.app.Session.ID {
@@ -97,17 +88,24 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case opencode.EventListResponseEventMessageUpdated:
if msg.Properties.Info.SessionID == m.app.Session.ID {
- m.renderView()
- if m.tail {
- m.viewport.GotoBottom()
- }
+ cmds = append(cmds, m.renderView())
}
case opencode.EventListResponseEventMessagePartUpdated:
if msg.Properties.Part.SessionID == m.app.Session.ID {
- m.renderView()
- if m.tail {
- m.viewport.GotoBottom()
- }
+ cmds = append(cmds, m.renderView())
+ }
+ case renderCompleteMsg:
+ m.partCount = msg.partCount
+ m.lineCount = msg.lineCount
+ m.rendering = false
+ m.loading = false
+ m.viewport.SetHeight(m.height - lipgloss.Height(m.header))
+ m.viewport.SetContent(msg.content)
+ if m.tail {
+ m.viewport.GotoBottom()
+ }
+ if m.dirty {
+ cmds = append(cmds, m.renderView())
}
}
@@ -119,144 +117,179 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Batch(cmds...)
}
-func (m *messagesComponent) renderView() {
- measure := util.Measure("messages.renderView")
- defer measure("messageCount", len(m.app.Messages))
+type renderCompleteMsg struct {
+ content string
+ partCount int
+ lineCount int
+}
+func (m *messagesComponent) renderView() tea.Cmd {
m.header = m.renderHeader()
- t := theme.CurrentTheme()
- blocks := make([]string, 0)
- m.partCount = 0
- m.lineCount = 0
+ if m.rendering {
+ m.dirty = true
+ return func() tea.Msg {
+ return nil
+ }
+ }
+ m.dirty = false
+ m.rendering = true
- orphanedToolCalls := make([]opencode.ToolPart, 0)
+ return func() tea.Msg {
+ measure := util.Measure("messages.renderView")
+ defer measure()
- width := min(m.width, app.MAX_CONTAINER_WIDTH)
- if m.app.Config.Layout == opencode.LayoutConfigStretch {
- width = m.width
- }
+ t := theme.CurrentTheme()
+ blocks := make([]string, 0)
+ partCount := 0
+ lineCount := 0
- for _, message := range m.app.Messages {
- var content string
- var cached bool
-
- switch casted := message.Info.(type) {
- case opencode.UserMessage:
- for partIndex, part := range message.Parts {
- switch part := part.(type) {
- case opencode.TextPart:
- if part.Synthetic {
- continue
- }
- remainingParts := message.Parts[partIndex+1:]
- fileParts := make([]opencode.FilePart, 0)
- for _, part := range remainingParts {
- switch part := part.(type) {
- case opencode.FilePart:
- fileParts = append(fileParts, part)
+ orphanedToolCalls := make([]opencode.ToolPart, 0)
+
+ width := min(m.width, app.MAX_CONTAINER_WIDTH)
+ if m.app.Config.Layout == opencode.LayoutConfigStretch {
+ width = m.width
+ }
+
+ for _, message := range m.app.Messages {
+ var content string
+ var cached bool
+
+ switch casted := message.Info.(type) {
+ case opencode.UserMessage:
+ for partIndex, part := range message.Parts {
+ switch part := part.(type) {
+ case opencode.TextPart:
+ if part.Synthetic {
+ continue
}
- }
- flexItems := []layout.FlexItem{}
- if len(fileParts) > 0 {
- fileStyle := styles.NewStyle().Background(t.BackgroundElement()).Foreground(t.TextMuted()).Padding(0, 1)
- mediaTypeStyle := styles.NewStyle().Background(t.Secondary()).Foreground(t.BackgroundPanel()).Padding(0, 1)
- for _, filePart := range fileParts {
- mediaType := ""
- switch filePart.Mime {
- case "text/plain":
- mediaType = "txt"
- case "image/png", "image/jpeg", "image/gif", "image/webp":
- mediaType = "img"
- mediaTypeStyle = mediaTypeStyle.Background(t.Accent())
- case "application/pdf":
- mediaType = "pdf"
- mediaTypeStyle = mediaTypeStyle.Background(t.Primary())
+ remainingParts := message.Parts[partIndex+1:]
+ fileParts := make([]opencode.FilePart, 0)
+ for _, part := range remainingParts {
+ switch part := part.(type) {
+ case opencode.FilePart:
+ fileParts = append(fileParts, part)
}
- flexItems = append(flexItems, layout.FlexItem{
- View: mediaTypeStyle.Render(mediaType) + fileStyle.Render(filePart.Filename),
- })
}
- }
- bgColor := t.BackgroundPanel()
- files := layout.Render(
- layout.FlexOptions{
- Background: &bgColor,
- Width: width - 6,
- Direction: layout.Column,
- },
- flexItems...,
- )
-
- key := m.cache.GenerateKey(casted.ID, part.Text, width, files)
- content, cached = m.cache.Get(key)
- if !cached {
- content = renderText(
- m.app,
- message.Info,
- part.Text,
- m.app.Config.Username,
- m.showToolDetails,
- width,
- files,
- )
- content = lipgloss.PlaceHorizontal(
- m.width,
- lipgloss.Center,
- content,
- styles.WhitespaceStyle(t.Background()),
+ flexItems := []layout.FlexItem{}
+ if len(fileParts) > 0 {
+ fileStyle := styles.NewStyle().Background(t.BackgroundElement()).Foreground(t.TextMuted()).Padding(0, 1)
+ mediaTypeStyle := styles.NewStyle().Background(t.Secondary()).Foreground(t.BackgroundPanel()).Padding(0, 1)
+ for _, filePart := range fileParts {
+ mediaType := ""
+ switch filePart.Mime {
+ case "text/plain":
+ mediaType = "txt"
+ case "image/png", "image/jpeg", "image/gif", "image/webp":
+ mediaType = "img"
+ mediaTypeStyle = mediaTypeStyle.Background(t.Accent())
+ case "application/pdf":
+ mediaType = "pdf"
+ mediaTypeStyle = mediaTypeStyle.Background(t.Primary())
+ }
+ flexItems = append(flexItems, layout.FlexItem{
+ View: mediaTypeStyle.Render(mediaType) + fileStyle.Render(filePart.Filename),
+ })
+ }
+ }
+ bgColor := t.BackgroundPanel()
+ files := layout.Render(
+ layout.FlexOptions{
+ Background: &bgColor,
+ Width: width - 6,
+ Direction: layout.Column,
+ },
+ flexItems...,
)
- m.cache.Set(key, content)
- }
- if content != "" {
- m.partCount++
- m.lineCount += lipgloss.Height(content) + 1
- blocks = append(blocks, content)
- }
- }
- }
- case opencode.AssistantMessage:
- hasTextPart := false
- for partIndex, p := range message.Parts {
- switch part := p.(type) {
- case opencode.TextPart:
- hasTextPart = true
- finished := casted.Time.Completed > 0
- remainingParts := message.Parts[partIndex+1:]
- toolCallParts := make([]opencode.ToolPart, 0)
-
- // sometimes tool calls happen without an assistant message
- // these should be included in this assistant message as well
- if len(orphanedToolCalls) > 0 {
- toolCallParts = append(toolCallParts, orphanedToolCalls...)
- orphanedToolCalls = make([]opencode.ToolPart, 0)
+ key := m.cache.GenerateKey(casted.ID, part.Text, width, files)
+ content, cached = m.cache.Get(key)
+ if !cached {
+ content = renderText(
+ m.app,
+ message.Info,
+ part.Text,
+ m.app.Config.Username,
+ m.showToolDetails,
+ width,
+ files,
+ )
+ content = lipgloss.PlaceHorizontal(
+ m.width,
+ lipgloss.Center,
+ content,
+ styles.WhitespaceStyle(t.Background()),
+ )
+ m.cache.Set(key, content)
+ }
+ if content != "" {
+ partCount++
+ lineCount += lipgloss.Height(content) + 1
+ blocks = append(blocks, content)
+ }
}
+ }
- remaining := true
- for _, part := range remainingParts {
- if !remaining {
- break
+ case opencode.AssistantMessage:
+ hasTextPart := false
+ for partIndex, p := range message.Parts {
+ switch part := p.(type) {
+ case opencode.TextPart:
+ hasTextPart = true
+ finished := part.Time.End > 0
+ remainingParts := message.Parts[partIndex+1:]
+ toolCallParts := make([]opencode.ToolPart, 0)
+
+ // sometimes tool calls happen without an assistant message
+ // these should be included in this assistant message as well
+ if len(orphanedToolCalls) > 0 {
+ toolCallParts = append(toolCallParts, orphanedToolCalls...)
+ orphanedToolCalls = make([]opencode.ToolPart, 0)
}
- switch part := part.(type) {
- case opencode.TextPart:
- // we only want tool calls associated with the current text part.
- // if we hit another text part, we're done.
- remaining = false
- case opencode.ToolPart:
- toolCallParts = append(toolCallParts, part)
- if part.State.Status != opencode.ToolPartStateStatusCompleted && part.State.Status != opencode.ToolPartStateStatusError {
- // i don't think there's a case where a tool call isn't in result state
- // and the message time is 0, but just in case
- finished = false
+
+ remaining := true
+ for _, part := range remainingParts {
+ if !remaining {
+ break
+ }
+ switch part := part.(type) {
+ case opencode.TextPart:
+ // we only want tool calls associated with the current text part.
+ // if we hit another text part, we're done.
+ remaining = false
+ case opencode.ToolPart:
+ toolCallParts = append(toolCallParts, part)
+ if part.State.Status != opencode.ToolPartStateStatusCompleted && part.State.Status != opencode.ToolPartStateStatusError {
+ // i don't think there's a case where a tool call isn't in result state
+ // and the message time is 0, but just in case
+ finished = false
+ }
}
}
- }
- if finished {
- key := m.cache.GenerateKey(casted.ID, part.Text, width, m.showToolDetails)
- content, cached = m.cache.Get(key)
- if !cached {
+ if finished {
+ key := m.cache.GenerateKey(casted.ID, part.Text, width, m.showToolDetails)
+ content, cached = m.cache.Get(key)
+ if !cached {
+ content = renderText(
+ m.app,
+ message.Info,
+ part.Text,
+ casted.ModelID,
+ m.showToolDetails,
+ width,
+ "",
+ toolCallParts...,
+ )
+ content = lipgloss.PlaceHorizontal(
+ m.width,
+ lipgloss.Center,
+ content,
+ styles.WhitespaceStyle(t.Background()),
+ )
+ m.cache.Set(key, content)
+ }
+ } else {
content = renderText(
m.app,
message.Info,
@@ -273,54 +306,50 @@ func (m *messagesComponent) renderView() {
content,
styles.WhitespaceStyle(t.Background()),
)
- m.cache.Set(key, content)
}
- } else {
- content = renderText(
- m.app,
- message.Info,
- part.Text,
- casted.ModelID,
- m.showToolDetails,
- width,
- "",
- toolCallParts...,
- )
- content = lipgloss.PlaceHorizontal(
- m.width,
- lipgloss.Center,
- content,
- styles.WhitespaceStyle(t.Background()),
- )
- }
- if content != "" {
- m.partCount++
- m.lineCount += lipgloss.Height(content) + 1
- blocks = append(blocks, content)
- }
- case opencode.ToolPart:
- if !m.showToolDetails {
- if !hasTextPart {
- orphanedToolCalls = append(orphanedToolCalls, part)
+ if content != "" {
+ partCount++
+ lineCount += lipgloss.Height(content) + 1
+ blocks = append(blocks, content)
+ }
+ case opencode.ToolPart:
+ if !m.showToolDetails {
+ if !hasTextPart {
+ orphanedToolCalls = append(orphanedToolCalls, part)
+ }
+ continue
}
- continue
- }
- width := width
- if m.app.Config.Layout == opencode.LayoutConfigAuto &&
- part.Tool == "edit" &&
- part.State.Error == "" {
- width = min(m.width, app.EDIT_DIFF_MAX_WIDTH)
- }
+ width := width
+ if m.app.Config.Layout == opencode.LayoutConfigAuto &&
+ part.Tool == "edit" &&
+ part.State.Error == "" {
+ width = min(m.width, app.EDIT_DIFF_MAX_WIDTH)
+ }
- if part.State.Status == opencode.ToolPartStateStatusCompleted || part.State.Status == opencode.ToolPartStateStatusError {
- key := m.cache.GenerateKey(casted.ID,
- part.ID,
- m.showToolDetails,
- width,
- )
- content, cached = m.cache.Get(key)
- if !cached {
+ if part.State.Status == opencode.ToolPartStateStatusCompleted || part.State.Status == opencode.ToolPartStateStatusError {
+ key := m.cache.GenerateKey(casted.ID,
+ part.ID,
+ m.showToolDetails,
+ width,
+ )
+ content, cached = m.cache.Get(key)
+ if !cached {
+ content = renderToolDetails(
+ m.app,
+ part,
+ width,
+ )
+ content = lipgloss.PlaceHorizontal(
+ m.width,
+ lipgloss.Center,
+ content,
+ styles.WhitespaceStyle(t.Background()),
+ )
+ m.cache.Set(key, content)
+ }
+ } else {
+ // if the tool call isn't finished, don't cache
content = renderToolDetails(
m.app,
part,
@@ -332,69 +361,56 @@ func (m *messagesComponent) renderView() {
content,
styles.WhitespaceStyle(t.Background()),
)
- m.cache.Set(key, content)
}
- } else {
- // if the tool call isn't finished, don't cache
- content = renderToolDetails(
- m.app,
- part,
- width,
- )
- content = lipgloss.PlaceHorizontal(
- m.width,
- lipgloss.Center,
- content,
- styles.WhitespaceStyle(t.Background()),
- )
- }
- if content != "" {
- m.partCount++
- m.lineCount += lipgloss.Height(content) + 1
- blocks = append(blocks, content)
+ if content != "" {
+ partCount++
+ lineCount += lipgloss.Height(content) + 1
+ blocks = append(blocks, content)
+ }
}
}
}
- }
- error := ""
- if assistant, ok := message.Info.(opencode.AssistantMessage); ok {
- switch err := assistant.Error.AsUnion().(type) {
- case nil:
- case opencode.AssistantMessageErrorMessageOutputLengthError:
- error = "Message output length exceeded"
- case opencode.ProviderAuthError:
- error = err.Data.Message
- case opencode.MessageAbortedError:
- error = "Request was aborted"
- case opencode.UnknownError:
- error = err.Data.Message
+ error := ""
+ if assistant, ok := message.Info.(opencode.AssistantMessage); ok {
+ switch err := assistant.Error.AsUnion().(type) {
+ case nil:
+ case opencode.AssistantMessageErrorMessageOutputLengthError:
+ error = "Message output length exceeded"
+ case opencode.ProviderAuthError:
+ error = err.Data.Message
+ case opencode.MessageAbortedError:
+ error = "Request was aborted"
+ case opencode.UnknownError:
+ error = err.Data.Message
+ }
}
- }
- if error != "" {
- error = styles.NewStyle().Width(width - 6).Render(error)
- error = renderContentBlock(
- m.app,
- error,
- width,
- WithBorderColor(t.Error()),
- )
- error = lipgloss.PlaceHorizontal(
- m.width,
- lipgloss.Center,
- error,
- styles.WhitespaceStyle(t.Background()),
- )
- blocks = append(blocks, error)
- m.lineCount += lipgloss.Height(error) + 1
+ if error != "" {
+ error = styles.NewStyle().Width(width - 6).Render(error)
+ error = renderContentBlock(
+ m.app,
+ error,
+ width,
+ WithBorderColor(t.Error()),
+ )
+ error = lipgloss.PlaceHorizontal(
+ m.width,
+ lipgloss.Center,
+ error,
+ styles.WhitespaceStyle(t.Background()),
+ )
+ blocks = append(blocks, error)
+ lineCount += lipgloss.Height(error) + 1
+ }
}
- }
- m.viewport.SetHeight(m.height - lipgloss.Height(m.header))
- m.viewport.SetContent("\n" + strings.Join(blocks, "\n\n"))
- if m.tail {
- m.viewport.GotoBottom()
+ content := "\n" + strings.Join(blocks, "\n\n")
+ return renderCompleteMsg{
+ content: content,
+ partCount: partCount,
+ lineCount: lineCount,
+ }
}
}
@@ -552,7 +568,7 @@ func formatTokensAndCost(
func (m *messagesComponent) View() string {
t := theme.CurrentTheme()
- if m.rendering {
+ if m.loading {
return lipgloss.Place(
m.width,
m.height,
@@ -569,10 +585,7 @@ func (m *messagesComponent) View() string {
}
func (m *messagesComponent) Reload() tea.Cmd {
- return func() tea.Msg {
- m.renderView()
- return renderFinishedMsg{}
- }
+ return m.renderView()
}
func (m *messagesComponent) PageUp() (tea.Model, tea.Cmd) {