Skip to content
Merged

0.9.1 #128

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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.9.1] - 2026-07-11

### Fixed

- 🏠 **Home chats work like workspace chats.** Home keeps chats, terminals, and browser pages alive while you switch tabs, streams replies as they arrive, and correctly loads global skills.

## [0.9.0] - 2026-07-11

### Added
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

![Open WebUI Computer Demo](./demo.png)

Open WebUI Computer (`cptr`) runs on your machine and serves your whole computer to any browser: files, terminal, editor, git, running sessions, AI agents, and tools. It literally is your computer.
Open WebUI Computer (`cptr`) runs on your machine and serves your whole computer to any browser: files, terminal, editor, git, browser tabs, running sessions, AI agents, and tools. It literally is your computer.

Use it from your phone, tablet, laptop, another computer, or the machine it's running on. Designed to feel native on every screen. Connect your own AI via API key, plug in a coding agent you already subscribe to, or work directly in the terminal. One tool, full workstation, any device.

Expand Down Expand Up @@ -65,10 +65,11 @@ Open WebUI Computer is the real workstation surface: files, shell, git state, wo
| ✏️ **Editor** | Syntax-highlighted editing with tabs. Open multiple files side by side. |
| 🔀 **Git** | Stage, commit, diff, branch, push. Visual changes view. No command line required. |
| ⌨️ **Terminal** | Full shell in the browser. Run your tools, your scripts, or your favourite coding agent. |
| 🌐 **Browser** | Browse with Proxy, a private Managed Chrome profile, or your own Personal Chrome session. |
| 🔄 **Sessions persist** | Terminal keeps running when you close the tab. Come back on any device. |
| 🗂️ **Tabs** | Open terminals, files, chats, and tools in separate tabs. Rearrange or split your layout. |
| 🗂️ **Tabs** | Open browsers, terminals, files, chats, and tools in separate tabs. Rearrange or split your layout. |
| 📂 **Workspaces** | Multiple projects, one instance. Switch without losing your place. |
| 🔍 **Search** | Find files by name, search across file contents and chat history. ⌘K to find anything. |
| 🔍 **Search** | Find files by name or contents, then jump straight to a matching line. ⌘K also searches chat history. |
| 📱 **Mobile-first** | Not a desktop UI made smaller. Built for the screen in your pocket. |

## AI agent
Expand Down
15 changes: 11 additions & 4 deletions cptr/frontend/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -351,16 +351,23 @@
});
}

// Connect system events when workspace is active
// Chat events belong to the authenticated user, not a workspace.
$effect(() => {
if (authState === 'authenticated') {
socketStore.connect();
bindGlobalChatListener();
} else {
socketStore.disconnect();
}
});

// Filesystem events remain workspace-scoped.
$effect(() => {
const ws = $currentWorkspace;
if (ws) {
systemEvents.connect(ws.fileBrowserCwd || ws.path);
socketStore.connect();
bindGlobalChatListener();
} else {
systemEvents.disconnect();
socketStore.disconnect();
}
});

Expand Down
52 changes: 31 additions & 21 deletions cptr/frontend/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,37 @@
}}
/>
<div class="pane-content">
{#each homePane.tabs.filter((tab) => tab.type === 'chat') as tab (tab.id)}
<div class="persisted-tab" class:persisted-tab-hidden={tab.id !== homePane.activeTabId}>
<ChatPanel
chatId={tab.path?.startsWith('new-') || tab.path?.startsWith('pending-')
? undefined
: tab.path}
tabId={tab.id}
ontabupdate={(tabId, chatId, label) =>
updateHomeChatTab(tabId, chatId, label, homePane.id)}
onopenchat={(chatId) => openHomeChat(chatId, homePane.id)}
/>
</div>
{/each}
{#each homePane.tabs.filter((tab) => tab.type === 'terminal' && tab.sessionId) as tab (tab.id)}
<div class="persisted-tab" class:persisted-tab-hidden={tab.id !== homePane.activeTabId}>
<Terminal sessionId={tab.sessionId!} />
</div>
{/each}
{#each homePane.tabs.filter((tab) => tab.type === 'browser' && tab.browserSessionId) as tab (tab.id)}
<div class="persisted-tab" class:persisted-tab-hidden={tab.id !== homePane.activeTabId}>
<BrowserPreview
sessionId={tab.browserSessionId!}
groupId={homePane.id}
tabId={tab.id}
initialUrl={tab.path}
active={tab.id === homePane.activeTabId && homePane.id === $homeState.activeGroupId}
onTabUpdate={(label) => updateHomeBrowserTab(tab.id, label, homePane.id)}
onOpenBrowser={(url) => openHomeBrowser(url, homePane.id)}
/>
</div>
{/each}
{#if homeTab?.type === 'home'}
<div class="h-full overflow-y-auto px-6">
<div class="mx-auto flex min-h-full w-full max-w-md flex-col justify-center py-6">
Expand Down Expand Up @@ -1213,27 +1244,6 @@
{/if}
</div>
</div>
{:else if homeTab?.type === 'chat'}
<ChatPanel
chatId={homeTab.path?.startsWith('new-') || homeTab.path?.startsWith('pending-')
? undefined
: homeTab.path}
tabId={homeTab.id}
ontabupdate={(tabId, chatId, label) =>
updateHomeChatTab(tabId, chatId, label, homePane.id)}
onopenchat={(chatId) => openHomeChat(chatId, homePane.id)}
/>
{:else if homeTab?.type === 'terminal' && homeTab.sessionId}
<Terminal sessionId={homeTab.sessionId} />
{:else if homeTab?.type === 'browser' && homeTab.browserSessionId}
<BrowserPreview
sessionId={homeTab.browserSessionId}
groupId={homePane.id}
tabId={homeTab.id}
initialUrl={homeTab.path}
onTabUpdate={(label) => updateHomeBrowserTab(homeTab.id, label, homePane.id)}
onOpenBrowser={(url) => openHomeBrowser(url, homePane.id)}
/>
{/if}
</div>
{#if dragOverZone?.groupId === homePane.id}
Expand Down
2 changes: 1 addition & 1 deletion cptr/utils/skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ def discover_skills(workspace: str) -> list[SkillMeta]:
ws = Path(workspace)

# 1. Workspace-level skills (project-specific, git-trackable)
if ws.is_dir():
if workspace and ws.is_dir():
for rel_dir in WORKSPACE_SKILL_DIRS:
skills_dir = ws / rel_dir
for skill in _scan_skills_dir(skills_dir, source="workspace", workspace=workspace):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "cptr"
version = "0.9.0"
version = "0.9.1"
description = "Your computer, from anywhere. Code, manage, and control your machine from the web."
license = {file = "LICENSE"}
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading