refactor(workspace): match KnowledgeView mini-chat dock style

- Widget is now a bottom-anchored dock spanning the content width
  (max-width 1200px, rounded top corners, bg-secondary, upward shadow)
  instead of a floating corner box.
- Drop the closed state and ✕ button — widget is always present,
  matching KnowledgeView's pattern. Collapsed = input-only dock,
  expanded = full ChatPanel above the input.
- Grid ratio 1fr:2fr → 0.85fr:2.15fr so tasks get a touch less room
  and notes a touch more.
- Workspace note-rail (the notes picker column inside the editor)
  widens from 155px to 200px so note titles breathe.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-22 12:53:02 -04:00
parent 649c0f124b
commit f75c40b417
3 changed files with 17 additions and 50 deletions
+14 -47
View File
@@ -49,7 +49,7 @@ watch(
} }
); );
type WidgetState = 'closed' | 'collapsed' | 'expanded'; type WidgetState = 'collapsed' | 'expanded';
const widgetState = ref<WidgetState>('collapsed'); const widgetState = ref<WidgetState>('collapsed');
const chatPanelRef = ref<InstanceType<typeof ChatPanel> | null>(null); const chatPanelRef = ref<InstanceType<typeof ChatPanel> | null>(null);
@@ -89,7 +89,6 @@ function _storageKey(pid: number) {
return `workspace_conv_${pid}`; return `workspace_conv_${pid}`;
} }
function open() { widgetState.value = 'collapsed'; }
function expand() { function expand() {
widgetState.value = 'expanded'; widgetState.value = 'expanded';
nextTick(() => chatPanelRef.value?.focus()); nextTick(() => chatPanelRef.value?.focus());
@@ -98,7 +97,6 @@ function collapse() {
widgetState.value = 'collapsed'; widgetState.value = 'collapsed';
nextTick(() => inputBarRef.value?.focus()); nextTick(() => inputBarRef.value?.focus());
} }
function close() { widgetState.value = 'closed'; }
async function restart() { async function restart() {
const conv = await chatStore.createConversation(); const conv = await chatStore.createConversation();
@@ -166,19 +164,8 @@ defineExpose({ prefill });
</script> </script>
<template> <template>
<!-- Closed: small floating handle --> <!-- Bottom-anchored chat dock: collapsed (input only) or expanded (full panel) -->
<button
v-if="widgetState === 'closed'"
class="ws-chat-handle"
title="Open project chat"
@click="open"
>
💬 Chat
</button>
<!-- Collapsed or expanded: floating panel -->
<div <div
v-else
class="ws-chat-widget" class="ws-chat-widget"
:class="{ 'ws-chat-widget--expanded': widgetState === 'expanded' }" :class="{ 'ws-chat-widget--expanded': widgetState === 'expanded' }"
> >
@@ -214,7 +201,6 @@ defineExpose({ prefill });
<polyline points="6 9 12 15 18 9"/> <polyline points="6 9 12 15 18 9"/>
</svg> </svg>
</button> </button>
<button class="btn-icon-sm" title="Close chat" @click="close"></button>
</div> </div>
</div> </div>
@@ -248,51 +234,32 @@ defineExpose({ prefill });
</template> </template>
<style scoped> <style scoped>
.ws-chat-handle {
position: absolute;
right: 1rem;
bottom: 1rem;
z-index: 20;
padding: 0.5rem 0.9rem;
border-radius: 999px;
border: 1px solid var(--color-border);
background: var(--color-surface);
color: var(--color-text);
cursor: pointer;
font-size: 0.82rem;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}
.ws-chat-handle:hover {
border-color: var(--color-primary);
color: var(--color-primary);
}
.ws-chat-widget { .ws-chat-widget {
position: absolute; position: absolute;
right: 1rem; bottom: 0;
bottom: 1rem; left: 0;
right: 0;
max-width: var(--page-max-width);
margin-inline: auto;
z-index: 20; z-index: 20;
width: 380px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: var(--color-surface); background: var(--color-bg-secondary);
border: 1px solid var(--color-border); border-radius: 16px 16px 0 0;
border-radius: 12px; box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.35), 0 -1px 0 rgba(255, 255, 255, 0.05);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
overflow: hidden; overflow: hidden;
transition: height 0.2s ease, width 0.2s ease; transition: height 0.2s ease;
} }
.ws-chat-widget--expanded { .ws-chat-widget--expanded {
width: 420px; height: 500px;
height: 560px;
} }
.ws-chat-header { .ws-chat-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 8px 12px; padding: 8px 14px;
border-bottom: 1px solid var(--color-border); border-bottom: 1px solid rgba(255, 255, 255, 0.06);
flex-shrink: 0; flex-shrink: 0;
} }
.ws-chat-title { .ws-chat-title {
@@ -457,7 +457,7 @@ defineExpose({ reload: loadProjectNotes });
/* ── Left rail ── */ /* ── Left rail ── */
.note-rail { .note-rail {
width: 155px; width: 200px;
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
+2 -2
View File
@@ -45,8 +45,8 @@ const panelOpen = ref<{ tasks: boolean; notes: boolean }>({ tasks: true, notes:
const gridColumns = computed(() => { const gridColumns = computed(() => {
return [ return [
panelOpen.value.tasks ? "minmax(0, 1fr)" : "0px", panelOpen.value.tasks ? "minmax(0, 0.85fr)" : "0px",
panelOpen.value.notes ? "minmax(0, 2fr)" : "0px", panelOpen.value.notes ? "minmax(0, 2.15fr)" : "0px",
].join(" "); ].join(" ");
}); });