From 3ba79e6c0d407d72c20c7deaa6364cd2523e97db Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Fri, 18 Sep 2026 11:42:50 +0300 Subject: [PATCH] refactor(ui): share one collapsible content treatment (#721) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(ui): share one collapsible content treatment Three disclosures drove Reka directly with three different motion treatments, and the chat tool-call one named utilities that do not exist: `collapsible-up`/`collapsible-down` without the `animate-` prefix, so that collapse never animated and ignored reduced motion. Export the shared content treatment from the collapsible theme and use it in the chat tool call, reasoning, and frame preset disclosures. Their headers keep their own shape — a status icon, a thought icon, a text chevron — because they are structurally different from the labeled row disclosure. * docs: note the chat disclosure animation fix --- CHANGELOG.md | 2 ++ src/components/chat/ChatMessage.vue | 3 +- src/components/chat/ReasoningBlock.vue | 6 ++-- .../frame-presets/FramePresetsSection.vue | 32 +++++++++++-------- src/theme/collapsible/collapsible.ts | 10 ++++-- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 716498407..96d7ef52f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ ### Fixed +- Animate the AI chat tool-call disclosure, which expanded and collapsed without motion because its animation classes were misspelled. + - Explain why the local MCP server did not start — a missing `@open-pencil/mcp` install, a denied command, an early exit, a rejected local connection, or an unreachable address — with translated guidance and collapsible technical details instead of one generic health failure. - Find a globally installed `@open-pencil/mcp` when the desktop app is launched from the system shell, by searching the common global binary directories that a graphical launch omits from `PATH`. - Mark unsaved documents and ask whether to save before closing a tab, the desktop window, or the application, rather than relying on recovery alone. diff --git a/src/components/chat/ChatMessage.vue b/src/components/chat/ChatMessage.vue index 299df0c51..95a2f6e76 100644 --- a/src/components/chat/ChatMessage.vue +++ b/src/components/chat/ChatMessage.vue @@ -15,6 +15,7 @@ import AttachmentList from '@/components/chat/attachment/AttachmentList.vue' import ChatMarkdown from '@/components/chat/ChatMarkdown.vue' import ReasoningBlock from '@/components/chat/ReasoningBlock.vue' import IconButton from '@/components/ui/button/IconButton.vue' +import { collapsibleContentMotion } from '@/theme/collapsible/collapsible' import { classifyToolState } from './tool-state' @@ -142,7 +143,7 @@ function partKey(part: UIMessagePart, index: number): stri
{{
                   part.state === 'output-error' && part.errorText
diff --git a/src/components/chat/ReasoningBlock.vue b/src/components/chat/ReasoningBlock.vue
index e6184ccbe..905ad8b87 100644
--- a/src/components/chat/ReasoningBlock.vue
+++ b/src/components/chat/ReasoningBlock.vue
@@ -5,6 +5,7 @@ import { computed, ref, watch } from 'vue'
 
 import type { ReasoningDisplay } from '@/app/settings/preferences/store'
 import ChatMarkdown from '@/components/chat/ChatMarkdown.vue'
+import { collapsibleContentMotion } from '@/theme/collapsible/collapsible'
 
 const {
   display = 'collapsed',
@@ -84,10 +85,7 @@ function updateOpen(value: boolean): void {
         aria-hidden="true"
       />
     
-    
+    
       
diff --git a/src/components/properties/frame-presets/FramePresetsSection.vue b/src/components/properties/frame-presets/FramePresetsSection.vue index 59e92c899..1082a339c 100644 --- a/src/components/properties/frame-presets/FramePresetsSection.vue +++ b/src/components/properties/frame-presets/FramePresetsSection.vue @@ -5,6 +5,7 @@ import { useI18n } from '@open-pencil/vue' import { useEditorStore } from '@/app/editor/active-store' import { FRAME_PRESET_CATEGORIES, type FramePreset } from '@/app/editor/frame-presets' +import { collapsibleContentMotion } from '@/theme/collapsible/collapsible' const store = useEditorStore() const { panels } = useI18n() @@ -40,20 +41,23 @@ function createFrame(preset: FramePreset) { {{ panels[category.labelKey] }} - - + + +
+ +
diff --git a/src/theme/collapsible/collapsible.ts b/src/theme/collapsible/collapsible.ts index e4a763f1b..494d2f43b 100644 --- a/src/theme/collapsible/collapsible.ts +++ b/src/theme/collapsible/collapsible.ts @@ -1,5 +1,12 @@ import { tv } from 'tailwind-variants' +/** + * Shared content treatment for every collapsible, including the call sites that + * keep their own header shape and drive Reka directly. + */ +export const collapsibleContentMotion = + 'overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up duration-180 motion-reduce:data-[state=open]:animate-none motion-reduce:data-[state=closed]:animate-none' + export const collapsibleTheme = { slots: { root: '', @@ -11,8 +18,7 @@ export const collapsibleTheme = { actions: 'shrink-0', // Padding and borders belong to the slot inside this wrapper so they cannot // snap while the measured height animates. - content: - 'overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up duration-180 motion-reduce:data-[state=open]:animate-none motion-reduce:data-[state=closed]:animate-none' + content: collapsibleContentMotion } } as const