Per-conv streaming state, immediate message_count commit, auto-new-chat on open

stores/chat.ts:
- Replace 7 global stream refs with convStreams: Record<number, ConvStreamState>
- Each conversation tracks its own stream state; 7 names become computed getters
- Add isStreamingConv(id) public helper
- Increment message_count +2 immediately after POST 202 (not at done) so the
  cleanup watcher never deletes a mid-generation conversation
- SSE reconnect retries now run for background conversations regardless of
  which conv is currently viewed
- error toasts only shown when the erroring conv is currently viewed
- deleteConversation cleans up orphaned stream state

ChatView.vue:
- Remove invalid write to store.lastContextMeta (now a read-only computed)
- Add !store.isStreamingConv(newId) guard to watch(convId) fetch so navigating
  back to a generating conversation shows accumulated content without stale refetch
- Add startNewConversation() helper; opening /chat with no ID auto-creates a
  new conversation and redirects to it (on mount and on navigation)
- Deleting a conversation also auto-creates a new one

Also committing previous-session changes (keyboard nav + task routing):
- App.vue: e shortcut only on note-view (not task-view, which is removed)
- TaskCard.vue: route directly to /tasks/:id (viewer), remove View buttons
- router/index.ts: remove task-view route; /tasks/:id now maps to TaskEditorView
- TasksListView.vue: Enter key pushes to /tasks/:id

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 20:32:56 -05:00
parent 404d58d037
commit dc39a56293
7 changed files with 125 additions and 117 deletions
+1 -40
View File
@@ -1,5 +1,4 @@
<script setup lang="ts">
import { useRouter } from "vue-router";
import type { Task, TaskStatus } from "@/types/task";
import StatusBadge from "@/components/StatusBadge.vue";
import PriorityBadge from "@/components/PriorityBadge.vue";
@@ -12,7 +11,6 @@ const props = defineProps<{
compact?: boolean;
projectTitle?: string;
}>();
const router = useRouter();
const emit = defineEmits<{
"tag-click": [tag: string];
"status-toggle": [id: number, status: TaskStatus];
@@ -49,7 +47,7 @@ function isOverdue(): boolean {
</script>
<template>
<router-link :to="`/tasks/${task.id}/edit`" :class="['task-card', { compact }]">
<router-link :to="`/tasks/${task.id}`" :class="['task-card', { compact }]">
<!-- Compact: single row -->
<template v-if="compact">
@@ -64,7 +62,6 @@ function isOverdue(): boolean {
<span v-if="task.due_date" :class="['due-compact', { overdue: isOverdue() }]">
{{ task.due_date }}
</span>
<button class="btn-edit-compact" title="View detail" @click.prevent.stop="router.push(`/tasks/${props.task.id}`)">View</button>
</template>
<!-- Full: original layout -->
@@ -77,7 +74,6 @@ function isOverdue(): boolean {
/>
<PriorityBadge :priority="task.priority!" />
<h3 class="task-title">{{ task.title || "Untitled" }}</h3>
<button class="btn-edit" title="View detail" @click.prevent.stop="router.push(`/tasks/${props.task.id}`)">View</button>
</div>
<div v-if="task.body" class="task-preview prose" v-html="renderPreview(task.body)"></div>
<div class="task-meta">
@@ -177,26 +173,6 @@ function isOverdue(): boolean {
color: var(--color-danger, #e74c3c);
font-weight: 600;
}
.btn-edit-compact {
flex-shrink: 0;
padding: 0.15rem 0.45rem;
font-size: 0.75rem;
background: none;
color: var(--color-text-muted);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
opacity: 0;
transition: opacity 0.15s, color 0.15s;
}
.task-card.compact:hover .btn-edit-compact {
opacity: 1;
}
.btn-edit-compact:hover {
color: var(--color-primary);
border-color: var(--color-primary);
}
/* Full layout */
.task-top {
display: flex;
@@ -213,21 +189,6 @@ function isOverdue(): boolean {
text-overflow: ellipsis;
white-space: nowrap;
}
.btn-edit {
flex-shrink: 0;
padding: 0.25rem 0.6rem;
font-size: 0.8rem;
background: var(--color-bg-card);
color: var(--color-text-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
transition: color 0.15s, border-color 0.15s;
}
.btn-edit:hover {
color: var(--color-primary);
border-color: var(--color-primary);
}
.task-preview {
margin: 0 0 0.5rem;
color: var(--color-text-secondary);