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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ 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.1.3] - 2026-06-06

### Fixed

- 📱 **Tool calls no longer overflow on mobile.** Fixed tool call rendering that could exceed the parent container width on narrow screens.
- 📱 **Settings tabs scroll horizontally on mobile.** The settings tab bar now scrolls on narrow screens instead of overflowing.

### Changed

- 🔤 **Tool call labels show filenames instead of full paths.** Tool call summaries like `Read`, `Edit`, and `Write` now display just the filename (e.g. `Read stores.ts`) instead of the full absolute path, keeping labels readable on all screen sizes.
- 📋 **Redesigned changelog modal.** The changelog is now presented as a clean, continuous vertical list with color-coded section badges.

## [0.1.2] - 2026-06-06

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion cptr/frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "0.1.2",
"version": "0.1.3",
"type": "module",
"scripts": {
"dev": "vite dev",
Expand Down
121 changes: 56 additions & 65 deletions cptr/frontend/src/lib/components/ChangelogModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,15 @@
type VersionData = { date: string; [section: string]: string | ChangelogEntry[] };

let changelog = $state<Record<string, VersionData> | null>(null);
let selectedVersion = $state<string | null>(null);
let error = $state(false);

const versions = $derived.by(() => (changelog ? Object.entries(changelog) : []));
const selectedData = $derived.by(() => {
if (!versions.length) return null;
const version = selectedVersion ?? versions[0][0];
return versions.find(([ver]) => ver === version) ?? versions[0];
});

$effect(() => {
if ($showChangelog && !changelog && !error) {
fetchJSON<Record<string, VersionData>>('/api/changelog')
.then((data) => {
changelog = data;
selectedVersion = Object.keys(data)[0] ?? null;
})
.catch(() => {
error = true;
Expand Down Expand Up @@ -68,10 +61,9 @@
<div class="flex items-center gap-3 px-4 pt-4 pb-2 shrink-0">
<div class="min-w-0 flex-1">
<h2 class="text-sm font-medium text-gray-900 dark:text-white">What's New</h2>
{#if selectedData}
{@const [ver, data] = selectedData}
{#if $appVersion}
<p class="mt-0.5 text-[11px] text-gray-400 dark:text-gray-600">
v{ver} · {formatDate(data.date)}
Release Notes
</p>
{/if}
</div>
Expand All @@ -85,63 +77,62 @@
</button>
</div>

{#if versions.length > 1}
<div class="flex gap-1 overflow-x-auto px-3 pb-2 shrink-0 scrollbar-hidden">
{#each versions as [ver], i (ver)}
<button
class="h-7 shrink-0 rounded-lg px-2 text-[11px] transition-colors duration-75
{(selectedVersion ?? versions[0][0]) === ver
? 'bg-gray-100 text-gray-900 dark:bg-white/6 dark:text-white'
: 'text-gray-400 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-300'}"
onclick={() => (selectedVersion = ver)}
>
v{ver}{#if i === 0}<span
class="ml-1 font-sans text-[9px] text-gray-300 dark:text-gray-700">latest</span
>{/if}
</button>
{/each}
</div>
{/if}

<div class="flex-1 min-h-0 overflow-y-auto px-4 py-3">
{#if selectedData}
{@const [_ver, data] = selectedData}
{#if versions.length > 0}
<div class="space-y-6">
{#each Object.entries(data).filter(([key]) => key !== 'date') as [section, items]}
{#if Array.isArray(items) && items.length > 0}
<section>
<h3
class="mb-2 text-[10px] font-medium uppercase tracking-wide text-gray-400 dark:text-gray-600"
>
{section}
</h3>
<ul class="space-y-2.5">
{#each items as entry}
<li class="flex gap-2.5 text-xs leading-relaxed">
<span
class="mt-[0.45em] h-1 w-1 shrink-0 rounded-full bg-gray-300 dark:bg-gray-700"
></span>
<div class="min-w-0">
{#if entry.title}
<span class="font-medium text-gray-900 dark:text-white"
>{entry.title}</span
>
{#if entry.content}
<span class="ml-1 text-gray-500 dark:text-gray-400"
>{entry.content}</span
>
{/if}
{:else}
<span class="text-gray-600 dark:text-gray-400"
>{entry.content || entry.raw}</span
>
{/if}
</div>
</li>
{/each}
</ul>
</section>
{/if}
{#each versions as [ver, data], i}
<div>
<div class="mb-2">
<h3 class="text-[13px] font-semibold text-gray-900 dark:text-white">v{ver}</h3>
<p class="text-[10px] text-gray-400 dark:text-gray-600 mt-0.5">{formatDate(data.date)}</p>
</div>

{#each Object.entries(data).filter(([key]) => key !== 'date') as [section, items]}
{#if Array.isArray(items) && items.length > 0}
<div class="mb-3">
<span
class="inline-block text-[10px] font-semibold uppercase tracking-wide rounded-full px-2 py-0.5 my-1.5
{section === 'added'
? 'bg-blue-500/10 text-blue-600 dark:text-blue-400'
: section === 'fixed'
? 'bg-green-500/10 text-green-600 dark:text-green-400'
: section === 'changed'
? 'bg-yellow-500/10 text-yellow-600 dark:text-yellow-400'
: section === 'removed'
? 'bg-red-500/10 text-red-600 dark:text-red-400'
: 'text-gray-400 dark:text-gray-600'}"
>
{section}
</span>
<ul class="space-y-2 mt-1.5">
{#each items as entry}
<li class="flex gap-2.5 text-xs leading-relaxed">
<span
class="mt-[0.45em] h-1 w-1 shrink-0 rounded-full bg-gray-300 dark:bg-gray-700"
></span>
<div class="min-w-0">
{#if entry.title}
<span class="font-medium text-gray-900 dark:text-white"
>{entry.title}</span
>
{#if entry.content}
<span class="ml-1 text-gray-500 dark:text-gray-400"
>{entry.content}</span
>
{/if}
{:else}
<span class="text-gray-600 dark:text-gray-400"
>{entry.content || entry.raw}</span
>
{/if}
</div>
</li>
{/each}
</ul>
</div>
{/if}
{/each}
</div>
{/each}
</div>
{:else if error}
Expand Down
4 changes: 2 additions & 2 deletions cptr/frontend/src/lib/components/SettingsModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
class="w-full max-w-3xl mx-4 md:mx-0 flex flex-col md:flex-row max-h-[85vh] md:h-[560px]"
>
<nav
class="shrink-0 border-b md:border-b-0 md:border-r border-gray-200 dark:border-white/6 md:w-[180px]"
class="shrink-0 min-w-0 overflow-x-auto md:overflow-x-visible scrollbar-hidden border-b md:border-b-0 md:border-r border-gray-200 dark:border-white/6 md:w-[180px]"
>
<div class="flex md:flex-col p-1 gap-px">
<div class="flex w-max min-w-full md:w-auto md:min-w-0 md:flex-col p-1 gap-px">
<button
class="flex items-center gap-1.5 h-7 px-2 md:w-full shrink-0 rounded-lg text-xs text-gray-400 dark:text-gray-600 hover:text-gray-700 dark:hover:text-gray-300 transition-colors duration-75 md:mb-1"
onclick={onclose}
Expand Down
44 changes: 26 additions & 18 deletions cptr/frontend/src/lib/components/chat/AssistantMessage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -126,23 +126,31 @@
}, 1500);
}

/** Shorten a file path to just the basename for compact display */
function shortPath(p: string | undefined): string {
if (!p) return '?';
const parts = p.split('/').filter(Boolean);
if (parts.length === 0) return p;
return parts[parts.length - 1];
}

/** Human-readable label for a tool call */
function toolLabel(name: string, args: any): string {
switch (name) {
case 'read_file': {
const range = args.start_line ? ` L${args.start_line}–${args.end_line || 'end'}` : '';
return `Read ${args.path || '?'}${range}`;
return `Read ${shortPath(args.path)}${range}`;
}
case 'edit_file':
return `Edit ${args.path || '?'}`;
return `Edit ${shortPath(args.path)}`;
case 'multi_edit_file':
return `Multi-edit ${args.path || '?'}`;
return `Multi-edit ${shortPath(args.path)}`;
case 'create_file':
return `Create ${args.path || '?'}`;
return `Create ${shortPath(args.path)}`;
case 'write_file':
return `Write ${args.path || '?'}`;
return `Write ${shortPath(args.path)}`;
case 'list_directory':
return `List ${args.path || '.'}${args.recursive ? ' (recursive)' : ''}`;
return `List ${shortPath(args.path)}${args.recursive ? ' (recursive)' : ''}`;
case 'search_files': {
const scope = args.include ? ` in ${args.include}` : '';
return `Search "${args.query || '?'}"${scope}`;
Expand Down Expand Up @@ -371,15 +379,15 @@
{@const isGroupOpen = expandedGroups.has(groupIdx)}
{@const hasPendingApproval = calls.some((c: any) => c.status === 'pending')}

<div class="w-full">
<div class="w-full min-w-0">
<!-- Group header -->
<button
class="w-fit text-left text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition cursor-pointer"
class="w-full min-w-0 text-left text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition cursor-pointer"
aria-label="Toggle tool calls"
aria-expanded={isGroupOpen}
onclick={() => toggleGroupExpanded(groupIdx)}
>
<div class="flex items-center gap-1.5 text-sm {hasPending ? 'shimmer' : ''}">
<div class="flex items-center gap-1.5 text-sm min-w-0 {hasPending ? 'shimmer' : ''}">
<!-- Status icon -->
{#if hasPending}
<div class="flex justify-center text-center">
Expand Down Expand Up @@ -465,7 +473,7 @@
{/if}

<!-- Summary text -->
<div class="flex-1 line-clamp-1">
<div class="flex-1 min-w-0 line-clamp-1">
<span class="text-gray-600 dark:text-gray-300"
>{hasPending ? 'Exploring' : 'Explored'}</span
>
Expand Down Expand Up @@ -513,14 +521,14 @@
{@const isExpanded = expandedCalls.has(callId)}
{@const pairedOutput = outputs.get(item.call_id)}

<div class="w-full">
<div class="w-full min-w-0">
<!-- Individual tool call row -->
<button
class="w-fit text-left text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition cursor-pointer"
class="w-full min-w-0 text-left text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition cursor-pointer"
onclick={() => toggleCallExpanded(callId)}
>
<div
class="flex items-center gap-1.5 text-sm {isExecuting ? 'shimmer' : ''}"
class="flex items-center gap-1.5 text-sm min-w-0 {isExecuting ? 'shimmer' : ''}"
>
<!-- Status icon -->
{#if isExecuting}
Expand Down Expand Up @@ -607,7 +615,7 @@
{/if}

<!-- Label -->
<div class="flex-1 line-clamp-1">
<div class="flex-1 min-w-0 line-clamp-1">
<span class="font-normal">{toolLabel(toolName, args)}</span>
</div>

Expand Down Expand Up @@ -664,7 +672,7 @@
{#if isExpanded}
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
<div
class="border border-gray-50 dark:border-gray-850/30 rounded-2xl my-1.5 p-3 space-y-3"
class="border border-gray-50 dark:border-gray-850/30 rounded-2xl my-1.5 p-3 space-y-3 overflow-hidden"
>
<!-- Input section -->
{#if Object.keys(args).length > 0}
Expand Down Expand Up @@ -709,7 +717,7 @@
{/if}
{:else if toolName === 'run_command'}
<code
class="block text-xs font-mono text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-900 rounded-lg px-2.5 py-1.5"
class="block text-xs font-mono text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-900 rounded-lg px-2.5 py-1.5 overflow-x-auto break-all whitespace-pre-wrap"
>{args.command}</code
>
{:else}
Expand Down Expand Up @@ -740,7 +748,7 @@
>
Output
</div>
<div class="w-full max-w-none">
<div class="w-full min-w-0 overflow-hidden">
<pre
class="text-xs text-gray-600 dark:text-gray-300 whitespace-pre-wrap break-words font-mono max-h-64 overflow-auto leading-relaxed">{pairedOutput
.output.length > 10000
Expand Down Expand Up @@ -770,7 +778,7 @@
<div class="mt-1 space-y-0.5">
{#each calls.filter((c: any) => c.status === 'pending') as item}
<div class="flex items-center gap-2 py-1 px-1">
<span class="text-xs text-gray-500 dark:text-gray-400 flex-1 line-clamp-1"
<span class="text-xs text-gray-500 dark:text-gray-400 flex-1 min-w-0 line-clamp-1"
>{toolLabel(item.name, item.arguments || {})}</span
>
<span class="flex gap-1 shrink-0">
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.1.2"
version = "0.1.3"
description = "Your computer, from anywhere. Code, manage, and control your machine from the web."
license = {file = "LICENSE"}
readme = "README.md"
Expand Down
Loading