2 Commits

Author SHA1 Message Date
196d28ca25 chore: bump version to 0.5.1
Some checks failed
Create Release / release (push) Has been cancelled
2026-01-07 20:51:56 +01:00
f3ba4c8876 fix: project deletion and replace confirm() with ConfirmDialog
Bug fixes:
- Fix project delete failing by adding db.chunks to transaction

UX improvements:
- Replace browser confirm() dialogs with styled ConfirmDialog component
- Affected: ProjectModal, ToolsTab, KnowledgeTab, PromptsTab, project page
2026-01-07 20:51:33 +01:00
8 changed files with 105 additions and 24 deletions

View File

@@ -18,7 +18,7 @@ import (
)
// Version is set at build time via -ldflags, or defaults to dev
var Version = "0.5.0"
var Version = "0.5.1"
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {

View File

@@ -1,6 +1,6 @@
{
"name": "vessel",
"version": "0.5.0",
"version": "0.5.1",
"private": true,
"type": "module",
"scripts": {

View File

@@ -5,6 +5,7 @@
import { projectsState, toastState } from '$lib/stores';
import type { Project } from '$lib/stores/projects.svelte.js';
import { addProjectLink, deleteProjectLink, getProjectLinks, type ProjectLink } from '$lib/storage/projects.js';
import { ConfirmDialog } from '$lib/components/shared';
interface Props {
isOpen: boolean;
@@ -26,6 +27,7 @@
let newLinkDescription = $state('');
let isLoading = $state(false);
let activeTab = $state<'settings' | 'instructions' | 'links'>('settings');
let showDeleteConfirm = $state(false);
// Predefined colors for quick selection
const presetColors = [
@@ -121,13 +123,14 @@
}
}
async function handleDelete() {
function handleDeleteClick() {
if (!projectId) return;
showDeleteConfirm = true;
}
if (!confirm('Delete this project? Conversations will be unlinked but not deleted.')) {
return;
}
async function handleDeleteConfirm() {
if (!projectId) return;
showDeleteConfirm = false;
isLoading = true;
try {
@@ -429,7 +432,7 @@
{#if projectId}
<button
type="button"
onclick={handleDelete}
onclick={handleDeleteClick}
disabled={isLoading}
class="rounded-lg px-4 py-2 text-sm font-medium text-red-500 transition-colors hover:bg-red-900/30 disabled:opacity-50"
>
@@ -458,3 +461,13 @@
</div>
</div>
{/if}
<ConfirmDialog
isOpen={showDeleteConfirm}
title="Delete Project"
message="Delete this project? Conversations will be unlinked but not deleted."
confirmText="Delete"
variant="danger"
onConfirm={handleDeleteConfirm}
onCancel={() => (showDeleteConfirm = false)}
/>

View File

@@ -14,6 +14,7 @@
} from '$lib/memory';
import type { StoredDocument } from '$lib/storage/db';
import { toastState, modelsState } from '$lib/stores';
import { ConfirmDialog } from '$lib/components/shared';
let documents = $state<StoredDocument[]>([]);
let stats = $state({ documentCount: 0, chunkCount: 0, totalTokens: 0 });
@@ -22,6 +23,7 @@
let uploadProgress = $state({ current: 0, total: 0 });
let selectedModel = $state(DEFAULT_EMBEDDING_MODEL);
let dragOver = $state(false);
let deleteConfirm = $state<{ show: boolean; doc: StoredDocument | null }>({ show: false, doc: null });
let fileInput: HTMLInputElement;
@@ -90,10 +92,14 @@
uploadProgress = { current: 0, total: 0 };
}
async function handleDelete(doc: StoredDocument) {
if (!confirm(`Delete "${doc.name}"? This cannot be undone.`)) {
return;
}
function handleDeleteClick(doc: StoredDocument) {
deleteConfirm = { show: true, doc };
}
async function confirmDelete() {
if (!deleteConfirm.doc) return;
const doc = deleteConfirm.doc;
deleteConfirm = { show: false, doc: null };
try {
await deleteDocument(doc.id);
@@ -232,7 +238,7 @@
<button
type="button"
onclick={() => handleDelete(doc)}
onclick={() => handleDeleteClick(doc)}
class="rounded p-2 text-theme-muted transition-colors hover:bg-red-900/30 hover:text-red-400"
aria-label="Delete document"
>
@@ -273,3 +279,13 @@
{/if}
</section>
</div>
<ConfirmDialog
isOpen={deleteConfirm.show}
title="Delete Document"
message={`Delete "${deleteConfirm.doc?.name}"? This cannot be undone.`}
confirmText="Delete"
variant="danger"
onConfirm={confirmDelete}
onCancel={() => (deleteConfirm = { show: false, doc: null })}
/>

View File

@@ -10,9 +10,11 @@
type PromptTemplate,
type PromptCategory
} from '$lib/prompts/templates';
import { ConfirmDialog } from '$lib/components/shared';
type Tab = 'my-prompts' | 'browse-templates';
let activeTab = $state<Tab>('my-prompts');
let deleteConfirm = $state<{ show: boolean; prompt: Prompt | null }>({ show: false, prompt: null });
let showEditor = $state(false);
let editingPrompt = $state<Prompt | null>(null);
@@ -106,10 +108,14 @@
}
}
async function handleDelete(prompt: Prompt): Promise<void> {
if (confirm(`Delete "${prompt.name}"? This cannot be undone.`)) {
await promptsState.remove(prompt.id);
}
function handleDeleteClick(prompt: Prompt): void {
deleteConfirm = { show: true, prompt };
}
async function confirmDelete(): Promise<void> {
if (!deleteConfirm.prompt) return;
await promptsState.remove(deleteConfirm.prompt.id);
deleteConfirm = { show: false, prompt: null };
}
async function handleSetDefault(prompt: Prompt): Promise<void> {
@@ -286,7 +292,7 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</button>
<button type="button" onclick={() => handleDelete(prompt)} class="rounded p-1.5 text-theme-muted hover:bg-red-900/30 hover:text-red-400" title="Delete">
<button type="button" onclick={() => handleDeleteClick(prompt)} class="rounded p-1.5 text-theme-muted hover:bg-red-900/30 hover:text-red-400" title="Delete">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
@@ -444,3 +450,13 @@
</div>
</div>
{/if}
<ConfirmDialog
isOpen={deleteConfirm.show}
title="Delete Prompt"
message={`Delete "${deleteConfirm.prompt?.name}"? This cannot be undone.`}
confirmText="Delete"
variant="danger"
onConfirm={confirmDelete}
onCancel={() => (deleteConfirm = { show: false, prompt: null })}
/>

View File

@@ -5,11 +5,13 @@
import { toolsState } from '$lib/stores';
import type { ToolDefinition, CustomTool } from '$lib/tools';
import { ToolEditor } from '$lib/components/tools';
import { ConfirmDialog } from '$lib/components/shared';
let showEditor = $state(false);
let editingTool = $state<CustomTool | null>(null);
let searchQuery = $state('');
let expandedDescriptions = $state<Set<string>>(new Set());
let deleteConfirm = $state<{ show: boolean; tool: CustomTool | null }>({ show: false, tool: null });
function openCreateEditor(): void {
editingTool = null;
@@ -32,9 +34,14 @@
}
function handleDeleteTool(tool: CustomTool): void {
if (confirm(`Delete "${tool.name}"? This cannot be undone.`)) {
toolsState.removeCustomTool(tool.id);
deleteConfirm = { show: true, tool };
}
function confirmDeleteTool(): void {
if (deleteConfirm.tool) {
toolsState.removeCustomTool(deleteConfirm.tool.id);
}
deleteConfirm = { show: false, tool: null };
}
const allTools = $derived(toolsState.getAllToolsWithState());
@@ -509,3 +516,13 @@
onClose={() => { showEditor = false; editingTool = null; }}
onSave={handleSaveTool}
/>
<ConfirmDialog
isOpen={deleteConfirm.show}
title="Delete Tool"
message={`Delete "${deleteConfirm.tool?.name}"? This cannot be undone.`}
confirmText="Delete"
variant="danger"
onConfirm={confirmDeleteTool}
onCancel={() => (deleteConfirm = { show: false, tool: null })}
/>

View File

@@ -175,7 +175,7 @@ export async function updateProject(
*/
export async function deleteProject(id: string): Promise<StorageResult<void>> {
return withErrorHandling(async () => {
await db.transaction('rw', [db.projects, db.projectLinks, db.conversations, db.documents, db.chatChunks], async () => {
await db.transaction('rw', [db.projects, db.projectLinks, db.conversations, db.documents, db.chunks, db.chatChunks], async () => {
// Unlink all conversations from this project
const conversations = await db.conversations.where('projectId').equals(id).toArray();
for (const conv of conversations) {

View File

@@ -20,6 +20,7 @@
import type { StoredDocument } from '$lib/storage/db';
import ProjectModal from '$lib/components/projects/ProjectModal.svelte';
import { searchProjectChatHistory, type ChatSearchResult } from '$lib/services/chat-indexer.js';
import { ConfirmDialog } from '$lib/components/shared';
// Get project ID from URL
const projectId = $derived($page.params.id);
@@ -50,6 +51,7 @@
let isSearching = $state(false);
let searchResults = $state<ChatSearchResult[]>([]);
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null;
let deleteDocConfirm = $state<{ show: boolean; doc: StoredDocument | null }>({ show: false, doc: null });
// Map of conversationId -> best matching snippet from search
const searchSnippetMap = $derived.by(() => {
@@ -323,8 +325,14 @@
await loadProjectData();
}
async function handleDeleteDocument(doc: StoredDocument) {
if (!confirm(`Delete "${doc.name}"? This cannot be undone.`)) return;
function handleDeleteDocumentClick(doc: StoredDocument) {
deleteDocConfirm = { show: true, doc };
}
async function confirmDeleteDocument() {
if (!deleteDocConfirm.doc) return;
const doc = deleteDocConfirm.doc;
deleteDocConfirm = { show: false, doc: null };
try {
await deleteDocument(doc.id);
@@ -632,7 +640,7 @@
</div>
<button
type="button"
onclick={() => handleDeleteDocument(doc)}
onclick={() => handleDeleteDocumentClick(doc)}
class="rounded p-1.5 text-theme-muted transition-colors hover:bg-red-900/30 hover:text-red-400"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
@@ -705,3 +713,14 @@
{projectId}
onUpdate={() => loadProjectData()}
/>
<!-- Delete Document Confirm -->
<ConfirmDialog
isOpen={deleteDocConfirm.show}
title="Delete Document"
message={`Delete "${deleteDocConfirm.doc?.name}"? This cannot be undone.`}
confirmText="Delete"
variant="danger"
onConfirm={confirmDeleteDocument}
onCancel={() => (deleteDocConfirm = { show: false, doc: null })}
/>