Skip to content

refactor tui#13

Merged
iohub merged 7 commits into
mainfrom
feat-0428
Apr 29, 2026
Merged

refactor tui#13
iohub merged 7 commits into
mainfrom
feat-0428

Conversation

@iohub
Copy link
Copy Markdown
Owner

@iohub iohub commented Apr 29, 2026

Summary by Sourcery

Refactor the TUI into an interactive, streaming task runner with a scrollable log and integrated assistant infrastructure, while simplifying assistant initialization and updating dependencies for the new UI capabilities.

New Features:

  • Add a live message log viewport with timestamped entries and styled rendering of AI responses, tool calls, and status messages.
  • Integrate background task execution into the TUI, allowing coding tasks to run asynchronously with real-time event streaming from the messaging system.
  • Introduce a history modal that can populate the current task input from past tasks and support search/filtering.
  • Automatically detect terminal background theme to choose matching markdown rendering styles in the TUI.

Enhancements:

  • Simplify the TUI input model to a single task input field and streamline keyboard shortcuts for submitting tasks, toggling language, and opening history.
  • Refine the welcome panel layout and banner styling to better present project context and recent activity.
  • Centralize assistant initialization for both TUI and HTTP modes by wiring CodingAssistant, TaskManager, and DataManager before entering the TUI loop.
  • Remove redundant conductor initialization logic in the CodingAssistant implementation.

Build:

  • Update Go version metadata and bump UI-related and markdown-rendering dependencies (lipgloss, glamour, and related libraries) required by the new TUI log and rendering features.

Documentation:

  • Add English and Chinese README sections showcasing TUI screenshots and update assistant titles and placeholders to reflect the CodeActor branding.

ydl added 7 commits April 29, 2026 06:50
…essage log

Replace the form-based submit-and-wait TUI with a single-line prompt
model that streams task events in real time. Key changes:

- Add message log area with styled event types (tool calls, AI
  responses, errors) rendered inline during task execution
- Run tasks in background goroutines, piping MessageEvents to the
  TUI via a channel-based consumer
- Move infrastructure initialization (config, client, assistant)
  out of startTUI so the TUI receives pre-built dependencies
- Remove button-based navigation in favor of keyboard shortcuts
- Simplify redundant nil-check init pattern in assistant
- Drop unused imports, dead code, and redundant comments
…le viewport

Replace the fixed-height message log with a scrollable viewport and use
glamour to render AI responses as formatted markdown. Viewport dimensions
are recalculated on terminal resize, and content is rebuilt from log
entries on each update. Non-AI-response entries fall back to plain text
formatting.
Detect terminal background color before entering raw mode and pass
an explicit dark/light style to glamour instead of relying on
WithAutoStyle(), which queried the terminal and leaked escape
sequences into the text input.
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 29, 2026

Reviewer's Guide

Refactors the TUI from a single-shot form submission into a streaming, log-centric Bubble Tea application integrated directly with the CodingAssistant and TaskManager, adds markdown rendering for AI responses, and simplifies main.go’s TUI mode wiring and i18n strings while updating dependencies for the new UI stack.

Sequence diagram for streaming task execution in TUI mode

sequenceDiagram
  actor User
  participant Main as main
  participant TUI as model
  participant TM as http_TaskManager
  participant CA as assistant_CodingAssistant
  participant DM as assistant_DataManager
  participant Disp as messaging_MessageDispatcher
  participant Cons as tuiEventConsumer

  User->>Main: start with mode tui
  Main->>CA: LoadConfig and NewClient and NewCodingAssistant
  Main->>TM: NewTaskManager
  Main->>DM: NewDataManager
  Main->>TUI: startTUI(taskFilePath, CA, TM, DM)
  TUI->>TUI: initialModel(preloadedTaskContent, CA, TM, DM, useDarkStyle)
  TUI->>TUI: Init() listenForEvents(eventCh)

  User->>TUI: type task and press enter
  TUI->>TUI: submitTask()
  TUI->>TUI: add status logEntry
  TUI->>TUI: rebuildViewportContent()
  TUI->>TUI: executeTaskCmd(taskDesc, projectDir, CA, TM, DM, eventCh)

  activate TUI
  TUI-->>TUI: returns tea.Cmd (background goroutine)
  deactivate TUI

  par background task execution
    TUI->>TM: AddTask(task)
    TUI->>Disp: NewMessageDispatcher
    TUI->>Cons: create tuiEventConsumer with ch
    TUI->>Disp: RegisterConsumer(Cons)
    TUI->>CA: IntegrateMessaging(Disp)
    TUI->>CA: ProcessCodingTaskWithCallback(request)
    loop task runs
      CA-->>Disp: Publish MessageEvent
      Disp-->>Cons: Deliver MessageEvent
      Cons-->>TUI: send event on eventCh
    end
    CA-->>TUI: result or error
    TUI->>DM: SaveTaskMemory(taskID, task.Memory)
    alt success
      TUI->>TM: SetTaskResult(taskID, result)
    else failure
      TUI->>TM: SetTaskError(taskID, err)
    end
    TUI-->>TUI: return taskCompleteMsg
  and UI event loop
    loop for each MessageEvent
      TUI->>TUI: listenForEvents(eventCh)
      TUI-->>TUI: taskEventMsg
      TUI->>TUI: formatEventAsEntry()
      TUI->>TUI: append logEntry
      TUI->>TUI: rebuildViewportContent()
    end
    TUI-->>TUI: taskCompleteMsg
    TUI->>TUI: handle taskCompleteMsg
    TUI->>TUI: taskRunning false and update errMsg and logEntries
    TUI->>TUI: rebuildViewportContent()
  end

  User-->>TUI: scroll viewport and view streaming log
Loading

Updated class diagram for TUI model and related types

classDiagram
  class model {
    // External dependencies
    +assistant_CodingAssistant assistant
    +http_TaskManager taskManager
    +assistant_DataManager dataManager
    // Input
    +textinput_Model input
    // Message log
    +[]logEntry logEntries
    +viewport_Model viewport
    +glamour_TermRenderer glamourRenderer
    +bool useDarkStyle
    // Task execution state
    +bool taskRunning
    +chan messaging_MessageEvent eventCh
    // Standard state
    +int termWidth
    +int termHeight
    +bool quitting
    +string errMsg
    +string infoMsg
    +Language currentLang
    +string projectDir
    // History modal state
    +bool showHistoryModal
    +[]assistant_TaskHistoryItem historyItems
    +[]assistant_TaskHistoryItem filteredItems
    +int historyIndex
    +textinput_Model historySearch
    +Init() tea_Cmd
    +Update(msg tea_Msg) tea_Model
    +View() string
    +toggleLanguage()
    +openHistoryModal()
    +applyHistorySelection()
    +filterHistoryList()
    +renderWelcomePanel() string
    +resizeViewport()
    +rebuildViewportContent()
    +submitTask() tea_Cmd
    +computeFieldWidth() int
    +renderHistoryModal() string
  }

  class logEntry {
    +time_Time timestamp
    +string eventType
    +string from
    +string content
    +string toolName
  }

  class taskEventMsg {
    +messaging_MessageEvent* event
  }

  class taskCompleteMsg {
    +string taskID
    +string result
    +error err
  }

  class tuiEventConsumer {
    +chan messaging_MessageEvent ch
    +Consume(event messaging_MessageEvent) error
  }

  class assistant_CodingAssistant {
    +ProcessCodingTaskWithCallback(req TaskRequest) (string, error)
    +ProcessConversation(req TaskRequest) (string, error)
    +Init(llm any, projectDir string)
    +IntegrateMessaging(dispatcher messaging_MessageDispatcher)
  }

  class http_TaskManager {
    +AddTask(task Task)
    +SetTaskResult(id string, result string)
    +SetTaskError(id string, err string)
    +GetTask(id string) (Task, bool)
  }

  class assistant_DataManager {
    +ListTaskHistory(limit int) ([]assistant_TaskHistoryItem, error)
    +SaveTaskMemory(taskID string, memory memory_ConversationMemory) error
  }

  class messaging_MessageEvent {
    +time_Time Timestamp
    +string Type
    +string From
    +any Content
  }

  class messaging_MessageDispatcher {
    +RegisterConsumer(consumer messaging_Consumer)
    +Shutdown()
  }

  class tea_Cmd
  class tea_Msg
  class tea_Model
  class textinput_Model
  class viewport_Model {
    +int Width
    +int Height
    +SetContent(content string)
    +GotoBottom()
    +Update(msg tea_Msg) (viewport_Model, tea_Cmd)
    +View() string
  }

  class glamour_TermRenderer {
    +Render(markdown string) (string, error)
  }

  class Language

  model --> assistant_CodingAssistant : uses
  model --> http_TaskManager : uses
  model --> assistant_DataManager : uses
  model --> textinput_Model : embeds
  model --> viewport_Model : embeds
  model --> glamour_TermRenderer : embeds
  model --> logEntry : contains
  model --> taskEventMsg : handles
  model --> taskCompleteMsg : handles
  tuiEventConsumer --> messaging_MessageEvent : consumes
  tuiEventConsumer --> model : sends via eventCh
  assistant_CodingAssistant --> messaging_MessageDispatcher : integrates

  class http_Task {
    +string ID
    +string Status
    +string ProjectDir
    +time_Time CreatedAt
    +time_Time UpdatedAt
    +memory_ConversationMemory Memory
    +context_Context Context
    +context_CancelFunc CancelFunc
  }

  class memory_ConversationMemory
  class context_Context
  class context_CancelFunc

  http_TaskManager --> http_Task : manages

  class TaskRequest {
    +context_Context ctx
    +string taskID
    +string projectDir
    +string taskDesc
    +string userMessage
    +memory_ConversationMemory memory
    +WithProjectDir(projectDir string) TaskRequest
    +WithTaskDesc(taskDesc string) TaskRequest
    +WithMemory(memory memory_ConversationMemory) TaskRequest
    +WithMessagePublisher(publisher assistant_MessagePublisher) TaskRequest
  }

  assistant_CodingAssistant --> TaskRequest : processes
Loading

File-Level Changes

Change Details Files
Refactor the Bubble Tea TUI model from a simple form to a streaming task runner with a scrollable log viewport and background task execution.
  • Replace the multi-input/focus-index form model with a single text input plus viewport-backed log display and new styling for log lines.
  • Introduce logEntry, taskEventMsg, taskCompleteMsg, and tuiEventConsumer types to route MessageEvents from the CodingAssistant into the TUI via a channel.
  • Add resizeViewport and rebuildViewportContent helpers to size the viewport based on terminal dimensions and render log entries, using glamour for markdown AI responses where possible.
  • Implement executeTaskCmd, submitTask, and listenForEvents to create and run tasks via TaskManager and CodingAssistant in a background goroutine and surface progress/results in the UI.
  • Simplify keyboard handling to focus on submit-on-enter, language toggle (ctrl+l), and history modal (ctrl+h), while removing tab-based focus navigation and the old status bar/buttons.
tui.go
Wire the TUI directly to assistant, HTTP task manager, and data manager from main.go instead of running a separate, blocking task flow after TUI exit.
  • Change startTUI to accept CodingAssistant, TaskManager, and DataManager dependencies and run entirely within the TUI loop without returning projectDir/taskDesc.
  • In main.go’s "tui" mode, initialize config, client, CodingAssistant, TaskManager, and DataManager once and pass them into startTUI, removing the previous post-TUI task execution logic and related imports.
  • Ensure errors during assistant initialization are logged and cause process exit before TUI startup.
tui.go
main.go
Adjust internationalization strings and branding to match the new TUI behavior and product name.
  • Rename the Chinese and English titles from "DeepCoder" to "CodeActor AI" variants.
  • Set TaskDescPlaceholder strings to empty for both languages, as the new TUI no longer displays the previous descriptive placeholder.
  • Tidy whitespace/alignment of the history-related translation fields.
i18n.go
Update third-party dependencies to support markdown rendering and newer Charmbracelet components.
  • Pin go to version 1.24.0 and bump lipgloss to a newer pre-release required for dark-background detection and viewport behavior.
  • Add charmbracelet/glamour and its transitive dependencies (chroma, goldmark, bluemonday, etc.) for markdown rendering in the TUI log viewport.
  • Update various indirect dependencies (regexp2, ansi, cellbuf, runewidth, x/sync, x/sys, x/text, etc.) to versions compatible with the updated UI stack.
go.mod
go.sum
Clean up CodingAssistant initialization behavior and improve project branding in documentation.
  • Simplify CodingAssistant.ProcessCodingTaskWithCallback and ProcessConversation to always re-init the conductor with the current projectDir without redundant nil checks.
  • Update ASCII banner art and styling to a new CodeActor-themed logo and adjust rainbow color list accordingly.
  • Add TUI screenshots to both English and Chinese READMEs for visual documentation of the new interface.
internal/assistant/assistant.go
tui.go
README.md
README_zh.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@iohub iohub merged commit 208c558 into main Apr 29, 2026
1 check passed
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 5 issues, and left some high level feedback:

  • The submitTask flow calls listenForEvents(m.eventCh) even though an event listener is already started in Init, which can lead to multiple goroutines consuming from the same channel and non-deterministic event ordering; consider centralizing the event listener setup so it is started only once.
  • In formatLogEntry, truncating content with content[:contentWidth-3] can cut through multi-byte runes and break alignment; consider truncating by runes or using a width-aware helper (e.g., based on runewidth/lipgloss) to avoid splitting characters.
  • The i18n strings (InfoMessage, TaskDescPlaceholder) still reference Tab/Shift+Tab navigation and non-empty placeholders, but the new TUI interaction model only uses a single input and Enter/Ctrl shortcuts; aligning these texts with the new UX would avoid confusing users.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `submitTask` flow calls `listenForEvents(m.eventCh)` even though an event listener is already started in `Init`, which can lead to multiple goroutines consuming from the same channel and non-deterministic event ordering; consider centralizing the event listener setup so it is started only once.
- In `formatLogEntry`, truncating `content` with `content[:contentWidth-3]` can cut through multi-byte runes and break alignment; consider truncating by runes or using a width-aware helper (e.g., based on `runewidth`/lipgloss) to avoid splitting characters.
- The i18n strings (`InfoMessage`, `TaskDescPlaceholder`) still reference Tab/Shift+Tab navigation and non-empty placeholders, but the new TUI interaction model only uses a single input and Enter/Ctrl shortcuts; aligning these texts with the new UX would avoid confusing users.

## Individual Comments

### Comment 1
<location path="tui.go" line_range="218-219" />
<code_context>
 	m.historySearch.Placeholder = langManager.GetText("HistorySearchHint")
 }

 func (m *model) openHistoryModal() {
-	// load history
 	dm, err := assistant.NewDataManager()
 	if err == nil {
 		items, err2 := dm.ListTaskHistory(50)
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Reuse the injected DataManager instead of constructing a new one in the history modal.

`model` already has a `dataManager *assistant.DataManager` from `initialModel`, but `openHistoryModal` always calls `assistant.NewDataManager()`. This may create multiple managers with inconsistent config or lifetime. Use `m.dataManager` instead, only creating a new one if it is nil.
</issue_to_address>

### Comment 2
<location path="tui.go" line_range="644-645" />
<code_context>
+	if contentWidth < 20 {
+		contentWidth = 20
+	}
+	if lipgloss.Width(content) > contentWidth {
+		content = content[:contentWidth-3] + "..."
+	}
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Truncate log content using runes rather than byte slices to avoid cutting multibyte characters.

Here you’re slicing `content` by bytes (`content[:contentWidth-3]`), so UTF-8 text can be cut in the middle of a multibyte rune, leading to invalid or garbled characters in the UI. Convert to `[]rune` (or use a rune-aware truncation helper) before truncating to ensure safe handling of non-ASCII content.
</issue_to_address>

### Comment 3
<location path="tui.go" line_range="539-541" />
<code_context>
+		b.WriteString("\n")
+	}
+
+	m.viewport.SetContent(b.String())
+	m.viewport.GotoBottom()
+}
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Only auto-scroll the viewport to the bottom when the user is already at (or near) the bottom.

This will force the view to jump to the latest entry even when the user has intentionally scrolled up. Track whether the viewport was at (or near) the bottom before updating, and only call `GotoBottom()` in that case.

```suggestion
	// Remember whether the viewport was at (or near) the bottom before updating.
	// This avoids forcing a scroll when the user has intentionally scrolled up.
	wasAtOrNearBottom := m.viewport.AtBottom() ||
		m.viewport.YOffset >= m.viewport.ContentHeight-m.viewport.Height-3

	m.viewport.SetContent(b.String())

	if wasAtOrNearBottom {
		m.viewport.GotoBottom()
	}
}
```
</issue_to_address>

### Comment 4
<location path="README.md" line_range="21-22" />
<code_context>
+## Screenshots
+
+<p align="center">
+  <img src="docs/sceenshot-1.png" alt="CodeActor TUI Screenshot 1" width="49%">
+  <img src="docs/sceenshot-2.png" alt="CodeActor TUI Screenshot 2" width="49%">
+</p>
+
</code_context>
<issue_to_address>
**issue (typo):** Typo in image filenames: "sceenshot" should be "screenshot".

Both images reference `docs/sceenshot-1.png` and `docs/sceenshot-2.png`. If these are typos, please rename the files and update the `src` paths to `screenshot-1.png` / `screenshot-2.png` to fix the spelling and avoid broken image links.

Suggested implementation:

```
## Screenshots

<p align="center">
  <img src="docs/screenshot-1.png" alt="CodeActor TUI Screenshot 1" width="49%">
  <img src="docs/screenshot-2.png" alt="CodeActor TUI Screenshot 2" width="49%">
</p>

```

To fully implement the suggestion, you should also:
1. Rename the actual image files in the `docs` directory from `sceenshot-1.png` to `screenshot-1.png` and from `sceenshot-2.png` to `screenshot-2.png`.
2. Update any other references in the repo (if any) that still use the old misspelled filenames.
</issue_to_address>

### Comment 5
<location path="README_zh.md" line_range="21-22" />
<code_context>
+## 效果截图
+
+<p align="center">
+  <img src="docs/sceenshot-1.png" alt="CodeActor TUI 截图 1" width="49%">
+  <img src="docs/sceenshot-2.png" alt="CodeActor TUI 截图 2" width="49%">
+</p>
+
</code_context>
<issue_to_address>
**issue (typo):** Typo in image filenames: "sceenshot" should be "screenshot".

Please update the `src` paths (and underlying filenames, if needed) so they consistently use `screenshot-1.png` and `screenshot-2.png` to avoid broken images.

```suggestion
  <img src="docs/screenshot-1.png" alt="CodeActor TUI 截图 1" width="49%">
  <img src="docs/screenshot-2.png" alt="CodeActor TUI 截图 2" width="49%">
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread tui.go
Comment on lines 218 to 219
func (m *model) openHistoryModal() {
// load history
dm, err := assistant.NewDataManager()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): Reuse the injected DataManager instead of constructing a new one in the history modal.

model already has a dataManager *assistant.DataManager from initialModel, but openHistoryModal always calls assistant.NewDataManager(). This may create multiple managers with inconsistent config or lifetime. Use m.dataManager instead, only creating a new one if it is nil.

Comment thread tui.go
Comment on lines +644 to +645
if lipgloss.Width(content) > contentWidth {
content = content[:contentWidth-3] + "..."
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Truncate log content using runes rather than byte slices to avoid cutting multibyte characters.

Here you’re slicing content by bytes (content[:contentWidth-3]), so UTF-8 text can be cut in the middle of a multibyte rune, leading to invalid or garbled characters in the UI. Convert to []rune (or use a rune-aware truncation helper) before truncating to ensure safe handling of non-ASCII content.

Comment thread tui.go
Comment on lines +539 to +541
m.viewport.SetContent(b.String())
m.viewport.GotoBottom()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): Only auto-scroll the viewport to the bottom when the user is already at (or near) the bottom.

This will force the view to jump to the latest entry even when the user has intentionally scrolled up. Track whether the viewport was at (or near) the bottom before updating, and only call GotoBottom() in that case.

Suggested change
m.viewport.SetContent(b.String())
m.viewport.GotoBottom()
}
// Remember whether the viewport was at (or near) the bottom before updating.
// This avoids forcing a scroll when the user has intentionally scrolled up.
wasAtOrNearBottom := m.viewport.AtBottom() ||
m.viewport.YOffset >= m.viewport.ContentHeight-m.viewport.Height-3
m.viewport.SetContent(b.String())
if wasAtOrNearBottom {
m.viewport.GotoBottom()
}
}

Comment thread README.md
Comment on lines +21 to +22
<img src="docs/sceenshot-1.png" alt="CodeActor TUI Screenshot 1" width="49%">
<img src="docs/sceenshot-2.png" alt="CodeActor TUI Screenshot 2" width="49%">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (typo): Typo in image filenames: "sceenshot" should be "screenshot".

Both images reference docs/sceenshot-1.png and docs/sceenshot-2.png. If these are typos, please rename the files and update the src paths to screenshot-1.png / screenshot-2.png to fix the spelling and avoid broken image links.

Suggested implementation:

## Screenshots

<p align="center">
  <img src="docs/screenshot-1.png" alt="CodeActor TUI Screenshot 1" width="49%">
  <img src="docs/screenshot-2.png" alt="CodeActor TUI Screenshot 2" width="49%">
</p>

To fully implement the suggestion, you should also:

  1. Rename the actual image files in the docs directory from sceenshot-1.png to screenshot-1.png and from sceenshot-2.png to screenshot-2.png.
  2. Update any other references in the repo (if any) that still use the old misspelled filenames.

Comment thread README_zh.md
Comment on lines +21 to +22
<img src="docs/sceenshot-1.png" alt="CodeActor TUI 截图 1" width="49%">
<img src="docs/sceenshot-2.png" alt="CodeActor TUI 截图 2" width="49%">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (typo): Typo in image filenames: "sceenshot" should be "screenshot".

Please update the src paths (and underlying filenames, if needed) so they consistently use screenshot-1.png and screenshot-2.png to avoid broken images.

Suggested change
<img src="docs/sceenshot-1.png" alt="CodeActor TUI 截图 1" width="49%">
<img src="docs/sceenshot-2.png" alt="CodeActor TUI 截图 2" width="49%">
<img src="docs/screenshot-1.png" alt="CodeActor TUI 截图 1" width="49%">
<img src="docs/screenshot-2.png" alt="CodeActor TUI 截图 2" width="49%">

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant