Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
9933a69
feat: add bot channel settings UI
SivanCola Jun 7, 2026
40d680a
feat: add guided bot connection setup
SivanCola Jun 7, 2026
6f311cb
移除 Tab 栏,将右侧面板切换按钮移到 topicbar 最右侧
ttmouse Jun 7, 2026
7681fc2
标题栏去掉重复的项目名前缀
ttmouse Jun 7, 2026
9ecfd15
fix: 二级菜单撑满侧边栏全宽
ttmouse Jun 7, 2026
aaf1ecb
移除侧边栏文件夹图标
ttmouse Jun 7, 2026
fc6a70d
调整二级菜单缩进,文字与一级菜单左对齐
ttmouse Jun 7, 2026
8611b00
fix: 侧边栏搜索栏被内容挤压变窄
ttmouse Jun 7, 2026
12b9b58
feat: 侧边栏 Projects 标题旁增加折叠/展开全部按钮
ttmouse Jun 7, 2026
fa14e12
侧边栏二级菜单多项 UI 优化
ttmouse Jun 7, 2026
e60c0a5
Fix project tree activity status
ttmouse Jun 7, 2026
6f2057a
Show git branch in workspace changes
ttmouse Jun 7, 2026
70cee35
chore(gitignore): add .alma-snapshots to ignore list
ttmouse Jun 7, 2026
2112380
fix(desktop): unread dot not visible due to undefined CSS variable
ttmouse Jun 7, 2026
e70f1f6
feat(desktop): sort sidebar topics by lastActivityAt instead of alpha…
ttmouse Jun 7, 2026
48c8300
chore(desktop): remove unused orderedTopicIDs function
ttmouse Jun 7, 2026
77c65c8
fix(desktop): replace remaining undefined --fg-muted in CSS
ttmouse Jun 7, 2026
7e160e8
feat(desktop): add refresh button to git branch indicator in changes tab
ttmouse Jun 7, 2026
5da8ea6
feat(desktop): add GitBranches and GitCheckout backend methods
ttmouse Jun 7, 2026
0db71de
feat(desktop): add branch switcher dropdown in changes panel
ttmouse Jun 7, 2026
2fe8e7c
feat: replace sidebar running indicator with spinning ring
ttmouse Jun 7, 2026
b8c6fe0
feat: add notification sound system with WAV file support
ttmouse Jun 7, 2026
88d891a
feat: integrate notification sounds into AskCard and settings
ttmouse Jun 7, 2026
c805166
feat: token-driven generative background music during AI generation
ttmouse Jun 7, 2026
e2f5229
tweak: 标题栏缩小/左侧边距缩小/分割线黑色间距移除
ttmouse Jun 7, 2026
d26112b
fix: 右侧分割线背景匹配 dock,移除底部 footer 上边框
ttmouse Jun 7, 2026
11d676a
fix: composer-card 负 margin 抵消边框偏移,与历史消息左对齐
ttmouse Jun 7, 2026
b2d7823
fix: 历史消息区 scrollbar-gutter:stable 解决滚动条偏移对齐
ttmouse Jun 7, 2026
1ce7610
fix: 移除 scrollbar-width:thin 改用原生 overlay 滚动条解决对齐
ttmouse Jun 7, 2026
f8f3457
fix: transcript overflow-y:overlay 使滚动条悬浮不占位,对齐输入框
ttmouse Jun 8, 2026
383e8c5
fix: 改用 overflow-y:overlay 使滚动条不占布局空间
ttmouse Jun 8, 2026
71d3ef6
feat: 添加 Cmd+I 快捷键调出 DevTools(需 --devtools 打包)
ttmouse Jun 8, 2026
2c8d047
fix: 用 WindowExecJS 替代 EventsEmit 触发 inspector
ttmouse Jun 8, 2026
642d6df
fix: scrollbar-gutter:stable 预占滚动条空间确保宽度一致
ttmouse Jun 8, 2026
1c6fd90
fix: .jump-bar 改为 position:absolute 完全脱离文档流
ttmouse Jun 8, 2026
b051cd9
fix(desktop): branch switcher reload file tree after checkout, enlarg…
ttmouse Jun 8, 2026
9d5f2de
feat(desktop): browser-style multi-tab right panel (DockTabBar + Brow…
ttmouse Jun 8, 2026
3a42fe3
refactor(sidebar): replace color dot with colored Folder icon, move c…
ttmouse Jun 8, 2026
59f05cd
refactor(ui): hide thinking label, replace custom brain icon with luc…
ttmouse Jun 8, 2026
741b39b
refactor(browser): remove iframe proxy, open URLs in system browser i…
ttmouse Jun 8, 2026
6cd0168
fix(desktop): 将 QuestionJumpBar 移出 transcript 滚动容器以固定在视口中央
ttmouse Jun 8, 2026
7de1615
Remove background from .process-card__body
ttmouse Jun 8, 2026
6abfda6
Remove composer caret (›) and slim input area
ttmouse Jun 8, 2026
f6686cf
Browser panel: inline webview with nav controls
ttmouse Jun 8, 2026
f3fa30a
Move send button to meta bar as multi-state action button
ttmouse Jun 8, 2026
d56aec2
feat(browser): add CDP interactive typing and element inspection
ttmouse Jun 8, 2026
c9ff770
fix: resolve merge compilation errors
ttmouse Jun 8, 2026
0a99d57
fix: resolve merge compilation errors (part 2)
ttmouse Jun 8, 2026
fc09688
fix: restore branch switcher in WorkspacePanel
ttmouse Jun 8, 2026
a665bf9
fix: show changed files list in changed view instead of file tree
ttmouse Jun 8, 2026
9b31430
fix: restore full branch switcher with changed files list
ttmouse Jun 8, 2026
363c3b3
feat(desktop): add scroll-to-bottom FAB button with Cmd+↓ shortcut
ttmouse Jun 8, 2026
9f99a76
fix(desktop): guard scroll-to-bottom from input hijack + scroll flicker
ttmouse Jun 8, 2026
479559d
feat(desktop): memory dock panel, default YOLO, status bar tooltips, …
ttmouse Jun 9, 2026
93bf2e9
feat(statusbar): show turn elapsed time alongside token count
ttmouse Jun 9, 2026
159dbbc
memory dock panel: redesign with existing CSS classes, remove Docs se…
ttmouse Jun 9, 2026
db49cc2
workspace panel: add preview dismiss with persistence, i18n keys for …
ttmouse Jun 9, 2026
12dc7e7
transcript card styling: remove border, add subtle card-bg, compact p…
ttmouse Jun 9, 2026
5208ffc
feat(memory): add global memory store with panel support; fix first-l…
ttmouse Jun 9, 2026
c2faddc
feat(dock): split changes tab into Changes + Commit tabs
ttmouse Jun 9, 2026
6ccd8da
fix(desktop): Git/commit panel 样式对齐 & 切换项目时刷新右侧面板
ttmouse Jun 9, 2026
71f3e8a
workspace: 移除分支切换 UI,新增 GitDiscardFile 丢弃文件改动
ttmouse Jun 9, 2026
e585b2e
Merge upstream/main-v2 into main-v2
ttmouse Jun 9, 2026
19edbd9
fixup: merge resolution — add workspaceDisplayName, tabRevealSignal, …
ttmouse Jun 9, 2026
d4e0749
Merge upstream/main-v2 into main-v2
ttmouse Jun 9, 2026
d83d864
feat(sidebar): 新增时间筛选「最新10条」选项
ttmouse Jun 9, 2026
80ae9e1
composer: 暂停按钮添加步进旋转动画
ttmouse Jun 9, 2026
e1db11f
sidebar: 时间筛选面板UI优化 & ApprovalModal添加提示音
ttmouse Jun 9, 2026
a1b6882
Merge upstream/main-v2 into main-v2 (bot settings + browser coexist)
ttmouse Jun 9, 2026
e6b74fa
fix: 恢复被 merge 覆盖的声音设置面板
ttmouse Jun 9, 2026
f2df285
fix: 恢复被 merge 覆盖的生成式背景音乐设置
ttmouse Jun 9, 2026
dbdaa69
style: 统一设置面板音效控件样式
ttmouse Jun 9, 2026
1226871
style: 统一设置面板下拉框宽度为160px
ttmouse Jun 9, 2026
917359d
Merge remote-tracking branch 'refs/remotes/upstream/main-v2' into mai…
ttmouse Jun 9, 2026
7a78ff6
feat: derive single-instance lock ID from executable path for multi-o…
ttmouse Jun 10, 2026
ea1f768
fix(macOS): reap entire codegraph process tree on exit (Setpgid + neg…
ttmouse Jun 9, 2026
06c7c02
Merge upstream/main-v2 into main-v2
ttmouse Jun 10, 2026
cdc5a36
fix: post-merge — fix TypeScript errors, deduplicate i18n keys, resto…
ttmouse Jun 10, 2026
c5e1a38
Merge remote-tracking branch 'remotes/upstream/main-v2' into main-v2
ttmouse Jun 10, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ Thumbs.db
# Scratch / agent working dirs (never committed)
/tmp/
/.codex/
.alma-snapshots
11 changes: 11 additions & 0 deletions desktop/Info.dev.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
182 changes: 172 additions & 10 deletions desktop/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
"log"
"mime"
"net/http"
"net/url"
Expand Down Expand Up @@ -39,6 +40,8 @@ import (
"reasonix/internal/plugin"
"reasonix/internal/provider"
"reasonix/internal/skill"

"reasonix/desktop/internal/browser"
)

// eventChannel is the Wails runtime event name the frontend subscribes to for the
Expand Down Expand Up @@ -75,6 +78,8 @@ type App struct {
tray *desktopTray

mediaTokens *mediaTokenStore

browser *browser.Service
botInstalls map[string]*botInstallSession
}

Expand Down Expand Up @@ -244,7 +249,7 @@ func (a *App) workspaceMediaMiddleware() func(http.Handler) http.Handler {
// NewApp constructs the bound object. Tabs are restored in startup from the
// last session's desktop-tabs.json.
func NewApp() *App {
return &App{tabs: map[string]*WorkspaceTab{}, mediaTokens: newMediaTokenStore(), botInstalls: map[string]*botInstallSession{}}
return &App{tabs: map[string]*WorkspaceTab{}, mediaTokens: newMediaTokenStore(), browser: browser.New(), botInstalls: map[string]*botInstallSession{}}
}

func (a *App) bootContext() context.Context {
Expand All @@ -269,6 +274,13 @@ func (a *App) startup(ctx context.Context) {
a.startTray()

go a.restoreOrBuildTabs()

// Start the browser service in the background (Chrome launch may take time).
go func() {
if err := a.browser.Start(ctx); err != nil {
log.Printf("[browser] failed to start: %v", err)
}
}()
}

func (a *App) beforeClose(ctx context.Context) bool {
Expand Down Expand Up @@ -406,10 +418,26 @@ func (a *App) restoreOrBuildTabs() {
return
}

// First launch: create a default Global tab.
tab := a.createTabEntry("global", globalTabWorkspaceRoot(), "")
// First launch: create a default Global tab with a real topicID so the
// project tree is immediately visible (the old empty-string topicID meant
// ensureTopicIndexed never fired, leaving ListProjectTree with nothing).
topic, err := a.CreateTopic("global", "", "")
if err != nil {
log.Printf("create default global topic: %v", err)
tab := a.createTabEntry("global", globalTabWorkspaceRoot(), "")
tab.sink = &tabEventSink{tabID: tab.ID, app: a, ctx: ctx}
tab.TopicTitle = "Global"
a.mu.Lock()
a.tabs[tab.ID] = tab
a.tabOrder = append(a.tabOrder, tab.ID)
a.activeTabID = tab.ID
a.mu.Unlock()
a.startTabControllerBuild(tab)
return
}
tab := a.createTabEntryWithID("global", globalTabWorkspaceRoot(), topic.ID, newTabID())
tab.sink = &tabEventSink{tabID: tab.ID, app: a, ctx: ctx}
tab.TopicTitle = "Global"
tab.TopicTitle = topic.Title
a.mu.Lock()
a.tabs[tab.ID] = tab
a.tabOrder = append(a.tabOrder, tab.ID)
Expand Down Expand Up @@ -468,6 +496,9 @@ func (a *App) shutdown(context.Context) {
t.Ctrl.Close()
}
}

// Stop the headless browser service.
a.browser.Stop()
}

// domReady is called (via OnDomReady) after the webview finishes loading its DOM
Expand Down Expand Up @@ -777,6 +808,9 @@ func (a *App) Compact() error {
}

// NewSession snapshots the current conversation and rotates to a fresh one.
// The tab's TopicID is cleared so the new blank session does not pollute the
// original topic's session lookup (the old session snapshot retains its topic
// association and remains findable via findTopicSession).
func (a *App) NewSession() error {
a.mu.RLock()
tab := a.activeTabLocked()
Expand All @@ -789,6 +823,16 @@ func (a *App) NewSession() error {
return err
}
a.persistTabSessionPath(tab, ctrl.SessionPath())

// Disassociate the tab from any topic so the new session is not mistaken for
// the original topic when the user later re-opens that topic from the sidebar
// or when findTopicSession scans session files by TopicID.
a.mu.Lock()
tab.TopicID = ""
tab.TopicTitle = ""
a.saveTabsLocked()
a.mu.Unlock()

return nil
}

Expand Down Expand Up @@ -3640,6 +3684,118 @@ func (a *App) RevealPath(path string) error {
return revealPath(path)
}

// OpenURL opens the given URL in the system default browser.
func (a *App) OpenURL(rawURL string) error {
rawURL = strings.TrimSpace(rawURL)
if rawURL == "" {
return os.ErrInvalid
}
if !strings.HasPrefix(rawURL, "http://") && !strings.HasPrefix(rawURL, "https://") {
rawURL = "https://" + rawURL
}
return exec.Command("open", rawURL).Start()
}

// --- Browser Control Methods (Wails-bound, auto-registered) ---

// BrowserNavigate tells the embedded headless browser to navigate to url.
// The url is normalized (https:// prefix added if missing). Returns
// "currentURL|||pageTitle" so the frontend can update the URL bar and tab title.
func (a *App) BrowserNavigate(rawURL string) (string, error) {
rawURL = strings.TrimSpace(rawURL)
if rawURL == "" {
return "", os.ErrInvalid
}
if !strings.HasPrefix(rawURL, "http://") && !strings.HasPrefix(rawURL, "https://") {
rawURL = "https://" + rawURL
}
return a.browser.Navigate(rawURL)
}

// BrowserBack navigates one step back in history.
func (a *App) BrowserBack() (string, error) {
return a.browser.Back()
}

// BrowserForward navigates one step forward in history.
func (a *App) BrowserForward() (string, error) {
return a.browser.Forward()
}

// BrowserRefresh reloads the current page.
func (a *App) BrowserRefresh() (string, error) {
return a.browser.Refresh()
}

// BrowserScreenshot takes a screenshot of the current page and returns it
// as a data-URL (base64-encoded PNG).
func (a *App) BrowserScreenshot() (string, error) {
return a.browser.Screenshot()
}

// BrowserEval executes JavaScript in the current page and returns the result.
func (a *App) BrowserEval(js string) (string, error) {
return a.browser.Eval(js)
}

// BrowserClick clicks on an element matching the given CSS selector.
func (a *App) BrowserClick(selector string) error {
return a.browser.Click(selector)
}

// BrowserClickAtPoint clicks at specific viewport coordinates (x, y).
func (a *App) BrowserClickAtPoint(x, y float64) error {
return a.browser.ClickAtPoint(x, y)
}

// BrowserType types text into an element matching the given CSS selector.
func (a *App) BrowserType(selector, text string) error {
return a.browser.Type(selector, text)
}

// BrowserTypeText types text into the currently focused page element.
// Used by the CDP screenshot interactive mode (user taps the "Type into page" bar).
func (a *App) BrowserTypeText(text string) error {
return a.browser.TypeText(text)
}

// BrowserScrollDown scrolls the page down by the given number of pixels.
func (a *App) BrowserScrollDown(pixels int) error {
return a.browser.ScrollDown(pixels)
}

// BrowserCurrentURL returns the current page URL and title as "url|||title".
func (a *App) BrowserCurrentURL() (string, error) {
u, title, err := a.browser.GetCurrentURL()
if err != nil {
return "", err
}
return fmt.Sprintf("%s|||%s", u, title), nil
}

// BrowserIsRunning reports whether the headless browser service is active.
func (a *App) BrowserIsRunning() bool {
return a.browser.IsRunning()
}

// BrowserSetViewportSize updates the headless browser viewport to match the
// sidebar iframe display dimensions, so element inspection coordinates are correct.
func (a *App) BrowserSetViewportSize(width, height int) error {
return a.browser.SetViewportSize(width, height)
}

// BrowserInspectElement finds the DOM element at viewport coordinates (x, y)
// and returns a JSON string with its tag, selector, text, outerHTML, and rect.
// Returns empty string if no element found.
func (a *App) BrowserInspectElement(x, y float64) string {
info, err := a.browser.InspectElement(x, y)
if err != nil || info == nil {
return ""
}
data, _ := json.Marshal(info)
return string(data)
}

func revealPath(path string) error {
switch goruntime.GOOS {
case "darwin":
Expand Down Expand Up @@ -3864,11 +4020,12 @@ type MemoryScope struct {
// MemoryView is the whole memory panel payload: hierarchical docs, saved facts,
// and the writable scopes for the quick-add selector.
type MemoryView struct {
Docs []MemoryDoc `json:"docs"`
Facts []MemoryFact `json:"facts"`
Scopes []MemoryScope `json:"scopes"`
StoreDir string `json:"storeDir"`
Available bool `json:"available"`
Docs []MemoryDoc `json:"docs"`
Facts []MemoryFact `json:"facts"`
GlobalFacts []MemoryFact `json:"globalFacts"`
Scopes []MemoryScope `json:"scopes"`
StoreDir string `json:"storeDir"`
Available bool `json:"available"`
}

// writableScopes are the quick-add targets the panel offers, broad → specific.
Expand All @@ -3880,7 +4037,7 @@ var writableScopes = []memory.Scope{memory.ScopeUser, memory.ScopeProject, memor
func (a *App) Memory() MemoryView {
// Always return non-nil slices: a nil Go slice marshals to JSON `null`, which
// would crash the panel's `view.facts.length` / `.map`.
view := MemoryView{Docs: []MemoryDoc{}, Facts: []MemoryFact{}, Scopes: []MemoryScope{}}
view := MemoryView{Docs: []MemoryDoc{}, Facts: []MemoryFact{}, GlobalFacts: []MemoryFact{}, Scopes: []MemoryScope{}}
a.mu.RLock()
ctrl := a.activeCtrlLocked()
a.mu.RUnlock()
Expand All @@ -3901,6 +4058,11 @@ func (a *App) Memory() MemoryView {
Name: f.Name, Title: f.Title, Description: f.Description, Type: string(f.Type), Body: f.Body,
})
}
for _, f := range set.GlobalStore.List() {
view.GlobalFacts = append(view.GlobalFacts, MemoryFact{
Name: f.Name, Title: f.Title, Description: f.Description, Type: string(f.Type), Body: f.Body,
})
}
for _, sc := range writableScopes {
if p := set.DocPath(sc); p != "" { // user scope yields "" when no config dir
view.Scopes = append(view.Scopes, MemoryScope{Scope: string(sc), Path: p})
Expand Down
49 changes: 49 additions & 0 deletions desktop/docs/git-branch-indicator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Git 分支指示器 — 方案说明

## 需求

桌面客户端的右侧面板「改动」视图中,没有显示当前 Git 分支名称。用户希望在工作区改动视图的搜索框上方看到当前分支。

## 实现方案

### 数据流

```
App.WorkspaceChanges() ← Go 后端绑定方法
├─ workspaceGitBranch(base) ← 新增:执行 git branch --show-current
├─ workspaceGitStatus(base) ← 已有
└─ WorkspaceChangesView ← 扩展:新增 GitBranch 字段
→ 前端 WorkspacePanel ← 渲染分支指示器
```

### 改动文件

| 文件 | 改动 |
|---|---|
| `desktop/workspace_changes.go` | 新增 `workspaceGitBranch()` 函数;在 `WorkspaceChanges()` 中调用并赋值;detached HEAD 时回退显示短 commit |
| `desktop/app.go` | `WorkspaceChangesView` 结构体添加 `GitBranch string` 字段 |
| `desktop/frontend/src/lib/types.ts` | `WorkspaceChangesView` 接口添加 `gitBranch?: string` |
| `desktop/frontend/src/components/WorkspacePanel.tsx` | 搜索框上方插入分支指示器 JSX |
| `desktop/frontend/src/styles.css` | 新增 `.workspace-branch-indicator` 样式 |

### 渲染位置

右侧面板 → 改动 tab → 在 `workspace-files__tools`(标签切换栏)和 `workspace-search`(搜索框)之间插入分支指示器。

仅在 `viewMode === "changed"` 且 `changes.gitBranch` 非空时显示。普通分支显示分支名;detached HEAD 显示 `@<short-sha>`。

### 效果

```
[GitBranch icon] main
┌─────────────────────┐
│ 🔍 Search files… │
└─────────────────────┘
<改动文件列表>
```

## 验证

- `go test . -run 'TestWorkspaceChanges|TestParseGitStatusPorcelainZ'`
- `npm run typecheck`
- `npm run check:css`
1 change: 0 additions & 1 deletion desktop/frontend/dist/.gitkeep

This file was deleted.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Loading