refactor(workspace): two-column grid; widget drives tool-call refresh

WorkspaceView now renders tasks (1fr) on the left and notes (2fr) on
the right with the floating WorkspaceChatWidget layered on top. The
widget owns the SSE streamingToolCalls watcher and emits note-changed
and task-changed events; the view listens and reloads the affected
panel refs.

Dropped from the view:
- ChatPanel mount, empty-state quick chips, and Chat header toggle
- The conversation lifecycle + SSE watcher (moved into the widget)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-22 11:09:50 -04:00
parent ae5c61a179
commit 6026c24450
2 changed files with 78 additions and 200 deletions
@@ -1,13 +1,54 @@
<script setup lang="ts">
import { ref, nextTick, onMounted, onUnmounted } from 'vue';
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue';
import { useChatStore } from '@/stores/chat';
import ChatPanel from '@/components/ChatPanel.vue';
import ChatInputBar from '@/components/ChatInputBar.vue';
const props = defineProps<{ projectId: number }>();
const emit = defineEmits<{
'note-changed': [noteId: number];
'task-changed': [];
}>();
const chatStore = useChatStore();
// SSE watcher — emit refresh events when tool calls succeed during streaming
const processedCount = ref(0);
watch(
() => chatStore.streamingToolCalls,
(calls) => {
for (let i = processedCount.value; i < calls.length; i++) {
const tc = calls[i];
if (tc.status !== 'success') continue;
// Note-editor refresh: any successful create_note/update_note
if (['create_note', 'update_note'].includes(tc.function) && tc.result?.data?.id) {
emit('note-changed', tc.result.data.id as number);
}
// Task-panel refresh: milestone changes, or note changes where the note is a task (has status)
const isTaskNoteChange =
['create_note', 'update_note'].includes(tc.function) &&
tc.result?.data?.status !== undefined;
const isMilestoneChange = ['create_milestone', 'update_milestone'].includes(tc.function);
if (isTaskNoteChange || isMilestoneChange) {
emit('task-changed');
}
}
processedCount.value = calls.length;
},
{ deep: true }
);
watch(
() => chatStore.streaming,
(s) => {
if (!s) processedCount.value = 0;
}
);
type WidgetState = 'closed' | 'collapsed' | 'expanded';
const widgetState = ref<WidgetState>('collapsed');