Skip to content

feat: update sidebar item ux#1391

Merged
zhangmo8 merged 5 commits intodevfrom
message-item-ux
Mar 26, 2026
Merged

feat: update sidebar item ux#1391
zhangmo8 merged 5 commits intodevfrom
message-item-ux

Conversation

@zhangmo8
Copy link
Copy Markdown
Collaborator

@zhangmo8 zhangmo8 commented Mar 25, 2026

before:

before.mp4

after:

f37d6cb7762fb7e19b961cf73f046c99_raw.mp4

Summary by CodeRabbit

  • New Features
    • Collapsible sidebar sections for pinned and grouped sessions with an empty state.
    • Dedicated pin button with animated pin/unpin “flight”, reduced‑motion support, and visible pin-feedback.
    • Loading shimmer and hero/pin-feedback visuals on session items.
  • Chores
    • Removed rename/clear actions from session item UI; delete remains.
  • Tests
    • Updated/added tests for collapsing, pin/delete interactions, and loading/pin-feedback visuals.
  • Localization
    • Added "Pinned" translations across multiple locales.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

Caution

Review failed

Pull request was closed or merged during review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces flat sidebar with collapsible pinned/group sections, implements animated (or reduced-motion) pin/unpin "pin flight" via DOM-clone and timed feedback, updates session-item props/emits/UI for pin/delete/hero/pin-feedback, removes rename/clear dialogs, updates tests and adds i18n sidebar.pinned keys.

Changes

Cohort / File(s) Summary
Sidebar container & pin-flight
src/renderer/src/components/WindowSideBar.vue
Adds sessionListRef, reactive pinned/group collapse tracking, pinFlightSessionId, pinFeedback state and cleanup. Implements prefers-reduced-motion branch and DOM-clone "pin flight" animation with scroll preservation, RAF-driven animation, commit timing, and timed pin/unpin feedback. Removes rename/clear dialog wiring.
Session item UI & API
src/renderer/src/components/WindowSideBarSessionItem.vue
Replaces row markup with explicit pin and delete <button>s using @click.stop, limits emits to select/toggle-pin/delete, adds props region, optional heroHidden, optional pinFeedbackMode, exposes data-* attributes for CSS effects, introduces loading-sheen, extensive scoped animations, and reduced-motion handling; removes rename/clear emits.
Tests
test/renderer/components/WindowSideBar.test.ts, test/renderer/components/WindowSideBarSessionItem.test.ts
Updates helpers/mount options for new props, switches assertions to ARIA/button-driven selectors, replaces context-menu/rename/clear tests with pin/delete and collapse/expand tests, adds loading-sheen and data-attribute assertions, and uses flushPromises() for async effects.
Localization
src/renderer/src/i18n/*/chat.json
src/renderer/src/i18n/en-US/chat.json, .../da-DK/..., .../fa-IR/..., .../fr-FR/..., .../he-IL/..., .../ja-JP/..., .../ko-KR/..., .../pt-BR/..., .../ru-RU/..., .../zh-CN/..., .../zh-HK/..., .../zh-TW/...
Adds new translation key sidebar.pinned across many locale JSON files for the pinned section label.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Item as WindowSideBarSessionItem
    participant Sidebar as WindowSideBar
    participant DOM
    participant RAF

    User->>Item: click pin button
    Item->>Sidebar: emit toggle-pin(session)
    Sidebar->>Sidebar: check prefers-reduced-motion
    alt reduced motion
        Sidebar->>Sidebar: commit pin toggle (no animation)
        Sidebar->>Item: update region / pinFeedbackMode
    else animated flow
        Sidebar->>DOM: clone source session element (fixed pos)
        Sidebar->>Sidebar: set pinFlightSessionId
        Sidebar->>Sidebar: commit pin state (mid-flight)
        Sidebar->>Sidebar: preserve/restore scrollTop
        Sidebar->>RAF: schedule animation frames
        RAF->>DOM: animate clone to destination
        RAF-->>Sidebar: animation complete
        Sidebar->>Item: set timed pinFeedbackMode
        Sidebar->>Sidebar: clear feedback timer
    end
    Sidebar->>Item: re-render with new data-* attributes
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐇 I dug a tunnel, gave a cheer,
A pin took flight and shimmered near,
Groups fold close, then open wide,
A cloned hop glides — what a ride!
Sidebar bright, I munch a carrot 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: update sidebar item ux' accurately reflects the main changes: replacing the sidebar's always-rendered structure with a collapsible design, updating session item UI components, and refining animations/interactions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch message-item-ux

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/src/components/WindowSideBar.vue`:
- Around line 323-325: The click handler reads session.isPinned into nextPinned
and races because pinFlightSessionId / pinFeedbackSessionId / pinFeedbackMode
are global single-slot flags; change the handler(s) that compute nextPinned (the
toggle/pin toggle functions around the usages of pinFlightSessionId,
pinFeedbackSessionId, pinFeedbackMode and nextPinned) to serialize toggles:
either (A) early-return if pinFlightSessionId or pinFeedbackSessionId is set for
that session, or (B) replace the single-slot refs with a per-session pending map
(e.g., pendingPinBySessionId) keyed by session.id so each session tracks its own
pending state; ensure the async store.update call is awaited before clearing the
pending flag and that the UI reads pending state per-session to prevent stale
nextPinned reads.

In `@src/renderer/src/components/WindowSideBarSessionItem.vue`:
- Around line 23-29: The component declares emits for 'rename' and 'clear' via
defineEmits in WindowSideBarSessionItem.vue but the template no longer renders
UI controls to trigger them, while WindowSideBar.vue still listens for those
events; either remove the unused emits/listener usage or re-add the UI actions
(buttons/menu items) that call emit('rename', session) and emit('clear',
session). Locate defineEmits in WindowSideBarSessionItem.vue and either delete
the 'rename' and 'clear' entries (and corresponding listeners in
WindowSideBar.vue) or restore the template action elements and their click
handlers so they call emit('rename', session) and emit('clear', session) where
session is the current UISession.
- Around line 40-48: The session row is a non-focusable div so keyboard users
cannot activate it; make it keyboard-accessible by either converting the div
into a semantic button or adding tabindex="0" and role="button" and a key
handler that triggers the same action as click. Specifically, keep the existing
attributes and class bindings on the element (the one with
`@click`="emit('select', session)"), add tabindex="0" and role="button" if you
don't convert to a <button>, and add a `@keydown` handler that listens for "Enter"
and " " (Space) and calls emit('select', session) (preventDefault for Space to
avoid scrolling). Ensure the handler uses the same emit('select', session)
invocation so keyboard and mouse activation behave identically.
- Around line 100-297: The scoped CSS introduces base/variant layout and colors
that should be moved into Tailwind utilities: remove/replace rules for
.session-item, .pin-button, .right-button, .session-title,
.session-title__label/.session-title__sheen and pin-button modifiers
(.pin-button--idle, .pin-button--active) with equivalent Tailwind classes in the
component template, keeping only the custom animations, keyframes, and complex
masks/gradients (e.g., ::after, ::before, data-pin-fx animations,
session-loading-sheen) in the <style> block; update class bindings to use
Tailwind utility names and preserve data-attribute selectors (data-pin-fx,
data-hero-hidden) to trigger the remaining animation CSS.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f2cd1cb-986d-4726-9bcb-765352540acf

📥 Commits

Reviewing files that changed from the base of the PR and between aa375cd and 390d454.

📒 Files selected for processing (4)
  • src/renderer/src/components/WindowSideBar.vue
  • src/renderer/src/components/WindowSideBarSessionItem.vue
  • test/renderer/components/WindowSideBar.test.ts
  • test/renderer/components/WindowSideBarSessionItem.test.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/renderer/src/components/WindowSideBar.vue (1)

335-338: ⚠️ Potential issue | 🟠 Major

Still serialize pin flights before starting another toggle.

These refs are still single-slot, but handleTogglePin() can start a second flight immediately. Pinning another session while one animation is in progress will overwrite the first session's heroHidden / feedback state and can leave the visual result desynced. Either gate new toggles until the current flight settles or track pending state per session.

Also applies to: 582-650, 655-665

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/WindowSideBar.vue` around lines 335 - 338,
handleTogglePin can start a new "flight" while pins are single-slot refs
(pinFlightSessionId, pinFeedbackSessionId, pinFeedbackMode), causing concurrent
toggles to overwrite heroHidden/feedback state; fix by serializing flights or
making state per-session: either add a gate boolean (e.g.,
isPinFlightInProgress) and return early from handleTogglePin until the current
flight settles (clear the gate when the animation/timeout or transitionend
completes), or replace the single-slot refs with a Map keyed by session id
(e.g., pinFlightState: Map<string, { heroHidden:boolean,
feedbackMode:PinFeedbackMode, pending:boolean }>) and update handleTogglePin to
set/clear the session's pending flag so simultaneous toggles target different
sessions without clobbering hero/feedback state; update any code reading
pinFlightSessionId/pinFeedbackSessionId/pinFeedbackMode to use the chosen
mechanism.
src/renderer/src/components/WindowSideBarSessionItem.vue (1)

38-58: ⚠️ Potential issue | 🟠 Major

Keyboard-only users still can't reach the row's primary actions.

Lines 39-45 keep session selection on a non-focusable div, and Lines 179-193 hide the idle pin button with visibility: hidden, which removes it from the tab order. On an unpinned session, a keyboard user currently can't open the thread or pin it. Please add a keyboard activation path for selection and keep the idle pin control focusable.

Also applies to: 179-193

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/WindowSideBarSessionItem.vue` around lines 38 -
58, The session row is not keyboard-accessible: make the root session container
(the div with class "session-item" that currently has `@click`="emit('select',
session)") keyboard-focusable by adding tabindex="0" and bind key handlers
(Enter/Space) to call the same emit('select', session) action so keyboard users
can activate selection; for the pin control (button with class "pin-button" and
:class based on session.isPinned) ensure the idle state is not removed from the
tab order—stop using visibility:hidden to hide it and instead keep the button
focusable (remove aria-hidden/visibility toggles or ensure tabindex is present
when idle) so `@click.stop`="emit('toggle-pin', session)" remains reachable via
keyboard and update any associated CSS that hides the idle pin to a visual-only
technique that preserves focusability.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/src/components/WindowSideBar.vue`:
- Around line 721-742: The CSS transition uses a hard-coded max-height (720px)
in the .sidebar-group-collapse-enter-to / .sidebar-group-collapse-leave-from
rules which causes clipping for taller groups; replace the fixed cap by
computing and applying the actual element.scrollHeight as an inline max-height
during the transition (use Vue transition hooks like
beforeEnter/enter/afterLeave in WindowSideBar.vue) or alternatively animate only
transform/opacity if you prefer no height animation; update the transition hooks
to set el.style.maxHeight = el.scrollHeight + 'px' on enter and
el.style.maxHeight = '0px' on leave and clear the style after the animation
completes so the collapse/expand is smooth for any group height.
- Around line 206-213: The folder icon binding is incorrectly using
isPinnedSectionCollapsed instead of the group's collapsed state; update the Icon
component that currently uses :icon="isPinnedSectionCollapsed ?
'lucide:folder-closed' : 'lucide:folder-open'" to derive its value from
isGroupCollapsed(group) (the same predicate used for the chevron) so each
group's folder-open/folder-closed icon stays in sync with
isGroupCollapsed(group).

---

Duplicate comments:
In `@src/renderer/src/components/WindowSideBar.vue`:
- Around line 335-338: handleTogglePin can start a new "flight" while pins are
single-slot refs (pinFlightSessionId, pinFeedbackSessionId, pinFeedbackMode),
causing concurrent toggles to overwrite heroHidden/feedback state; fix by
serializing flights or making state per-session: either add a gate boolean
(e.g., isPinFlightInProgress) and return early from handleTogglePin until the
current flight settles (clear the gate when the animation/timeout or
transitionend completes), or replace the single-slot refs with a Map keyed by
session id (e.g., pinFlightState: Map<string, { heroHidden:boolean,
feedbackMode:PinFeedbackMode, pending:boolean }>) and update handleTogglePin to
set/clear the session's pending flag so simultaneous toggles target different
sessions without clobbering hero/feedback state; update any code reading
pinFlightSessionId/pinFeedbackSessionId/pinFeedbackMode to use the chosen
mechanism.

In `@src/renderer/src/components/WindowSideBarSessionItem.vue`:
- Around line 38-58: The session row is not keyboard-accessible: make the root
session container (the div with class "session-item" that currently has
`@click`="emit('select', session)") keyboard-focusable by adding tabindex="0" and
bind key handlers (Enter/Space) to call the same emit('select', session) action
so keyboard users can activate selection; for the pin control (button with class
"pin-button" and :class based on session.isPinned) ensure the idle state is not
removed from the tab order—stop using visibility:hidden to hide it and instead
keep the button focusable (remove aria-hidden/visibility toggles or ensure
tabindex is present when idle) so `@click.stop`="emit('toggle-pin', session)"
remains reachable via keyboard and update any associated CSS that hides the idle
pin to a visual-only technique that preserves focusability.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ae6b0a4-ac72-485f-90bc-9ab2cb530656

📥 Commits

Reviewing files that changed from the base of the PR and between 390d454 and e3d623c.

📒 Files selected for processing (15)
  • src/renderer/src/components/WindowSideBar.vue
  • src/renderer/src/components/WindowSideBarSessionItem.vue
  • src/renderer/src/i18n/da-DK/chat.json
  • src/renderer/src/i18n/en-US/chat.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/ko-KR/chat.json
  • src/renderer/src/i18n/pt-BR/chat.json
  • src/renderer/src/i18n/ru-RU/chat.json
  • src/renderer/src/i18n/zh-CN/chat.json
  • src/renderer/src/i18n/zh-HK/chat.json
  • src/renderer/src/i18n/zh-TW/chat.json
  • test/renderer/components/WindowSideBar.test.ts
✅ Files skipped from review due to trivial changes (12)
  • src/renderer/src/i18n/da-DK/chat.json
  • src/renderer/src/i18n/zh-CN/chat.json
  • src/renderer/src/i18n/en-US/chat.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/zh-HK/chat.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/pt-BR/chat.json
  • src/renderer/src/i18n/zh-TW/chat.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/ko-KR/chat.json
  • src/renderer/src/i18n/ru-RU/chat.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/renderer/components/WindowSideBar.test.ts

Comment on lines +206 to +213
<Icon
:icon="isGroupCollapsed(group) ? 'lucide:chevron-right' : 'lucide:chevron-down'"
class="h-3 w-3 shrink-0"
/>
<span class="shrink-0 size-6 flex items-center justify-center">
<Icon
:icon="isPinnedSectionCollapsed ? 'lucide:folder-closed' : 'lucide:folder-open'"
class="size-4"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Drive the folder icon from isGroupCollapsed(group).

Lines 206-213 already use isGroupCollapsed(group) for the chevron, but Lines 212-213 still key the folder icon off isPinnedSectionCollapsed. That leaves every non-pinned group's folder-open/folder-closed indicator out of sync.

🩹 Proposed fix
-                  :icon="isPinnedSectionCollapsed ? 'lucide:folder-closed' : 'lucide:folder-open'"
+                  :icon="isGroupCollapsed(group) ? 'lucide:folder-closed' : 'lucide:folder-open'"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Icon
:icon="isGroupCollapsed(group) ? 'lucide:chevron-right' : 'lucide:chevron-down'"
class="h-3 w-3 shrink-0"
/>
<span class="shrink-0 size-6 flex items-center justify-center">
<Icon
:icon="isPinnedSectionCollapsed ? 'lucide:folder-closed' : 'lucide:folder-open'"
class="size-4"
<Icon
:icon="isGroupCollapsed(group) ? 'lucide:chevron-right' : 'lucide:chevron-down'"
class="h-3 w-3 shrink-0"
/>
<span class="shrink-0 size-6 flex items-center justify-center">
<Icon
:icon="isGroupCollapsed(group) ? 'lucide:folder-closed' : 'lucide:folder-open'"
class="size-4"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/WindowSideBar.vue` around lines 206 - 213, The
folder icon binding is incorrectly using isPinnedSectionCollapsed instead of the
group's collapsed state; update the Icon component that currently uses
:icon="isPinnedSectionCollapsed ? 'lucide:folder-closed' : 'lucide:folder-open'"
to derive its value from isGroupCollapsed(group) (the same predicate used for
the chevron) so each group's folder-open/folder-closed icon stays in sync with
isGroupCollapsed(group).

Comment on lines +721 to +742
.sidebar-group-collapse-enter-active,
.sidebar-group-collapse-leave-active {
overflow: hidden;
transition:
max-height 180ms ease,
opacity 160ms ease,
transform 180ms ease;
}

.sidebar-group-collapse-enter-from,
.sidebar-group-collapse-leave-to {
max-height: 0;
opacity: 0;
transform: translateY(-4px);
}

.sidebar-group-collapse-enter-to,
.sidebar-group-collapse-leave-from {
max-height: 720px;
opacity: 1;
transform: translateY(0);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The collapse transition will snap for taller groups.

Lines 737-740 cap the animation at max-height: 720px. Once a group grows past that, expand/collapse will clip and jump instead of transitioning cleanly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/WindowSideBar.vue` around lines 721 - 742, The
CSS transition uses a hard-coded max-height (720px) in the
.sidebar-group-collapse-enter-to / .sidebar-group-collapse-leave-from rules
which causes clipping for taller groups; replace the fixed cap by computing and
applying the actual element.scrollHeight as an inline max-height during the
transition (use Vue transition hooks like beforeEnter/enter/afterLeave in
WindowSideBar.vue) or alternatively animate only transform/opacity if you prefer
no height animation; update the transition hooks to set el.style.maxHeight =
el.scrollHeight + 'px' on enter and el.style.maxHeight = '0px' on leave and
clear the style after the animation completes so the collapse/expand is smooth
for any group height.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/renderer/src/components/WindowSideBar.vue (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Run Prettier to fix formatting issues.

The CI pipeline reports Prettier check failures. As per coding guidelines, run pnpm run format to fix code style issues before merging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/WindowSideBar.vue` at line 1, The file
WindowSideBar.vue has Prettier formatting failures; run the formatter (pnpm run
format) or directly run Prettier on the WindowSideBar.vue component to fix
spacing/indentation in the <template> block and any other sections, then stage
the formatted changes and push so CI passes.
♻️ Duplicate comments (3)
src/renderer/src/components/WindowSideBar.vue (3)

212-216: ⚠️ Potential issue | 🟡 Minor

Folder icon uses wrong collapsed state.

The folder icon is bound to isPinnedSectionCollapsed but should use isGroupCollapsed(group) to stay in sync with the chevron and actual group state.

🐛 Proposed fix
               <span class="shrink-0 size-6 flex items-center justify-center">
                 <Icon
-                  :icon="isPinnedSectionCollapsed ? 'lucide:folder-closed' : 'lucide:folder-open'"
+                  :icon="isGroupCollapsed(group) ? 'lucide:folder-closed' : 'lucide:folder-open'"
                   class="size-4"
                 />
               </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/WindowSideBar.vue` around lines 212 - 216, The
folder Icon currently checks isPinnedSectionCollapsed but should reflect each
group's real collapsed state; replace the binding to use isGroupCollapsed(group)
so the icon stays synchronized with the chevron and group collapse state (update
the Icon :icon prop where isPinnedSectionCollapsed is referenced inside the
WindowSideBar component to call isGroupCollapsed(group) instead).

722-735: ⚠️ Potential issue | 🟠 Major

Pin toggle lacks serialization guard.

Rapid clicks can race because nextPinned is derived from session.isPinned before the async store update completes, and the single-slot state refs (pinFlightSessionId, pinFeedbackSessionId) cannot track multiple concurrent toggles.

🛡️ Proposed fix: early-return if animation is in progress
 const handleTogglePin = async (session: UISession) => {
+  // Prevent concurrent toggles while animation/feedback is in progress
+  if (pinFlightSessionId.value === session.id || pinFeedbackSessionId.value === session.id) {
+    return
+  }
+
   const nextPinned = !session.isPinned

   try {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/WindowSideBar.vue` around lines 722 - 735, The
pin toggle races when users click rapidly because handleTogglePin computes
nextPinned from session.isPinned and launches async work without a serialization
guard; fix by checking and setting the single-slot guard (pinFlightSessionId) at
the start of handleTogglePin for the given session id (early-return if another
flight is active for that session), compute nextPinned after acquiring the guard
(or read the latest store/session state right before calling
commitPinToggle/animatePinFlight), then clear the guard in a finally block;
ensure you also avoid stomping pinFeedbackSessionId during an in-flight toggle
and reference the existing functions/refs: handleTogglePin, commitPinToggle,
animatePinFlight, pinFlightSessionId, and pinFeedbackSessionId.

804-809: ⚠️ Potential issue | 🟡 Minor

Hard-coded max-height may cause transition jump for tall groups.

The max-height: 720px cap will cause the expand/collapse transition to clip and snap for groups containing more than ~18-20 sessions. Consider using Vue transition hooks to dynamically set max-height to the element's scrollHeight.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/WindowSideBar.vue` around lines 804 - 809, The
CSS rule using .sidebar-group-collapse-enter-to and
.sidebar-group-collapse-leave-from with a hard-coded max-height: 720px causes
clipping for tall groups; in WindowSideBar.vue replace this static cap by
removing the fixed max-height and implement Vue transition hooks (e.g.
beforeEnter/enter/afterEnter and beforeLeave/leave/afterLeave) on the transition
wrapping the group: in beforeEnter set el.style.maxHeight = '0px', in enter set
el.style.maxHeight = el.scrollHeight + 'px' and clear it in afterEnter, and for
collapse set el.style.maxHeight = el.scrollHeight + 'px' in beforeLeave then to
'0px' in leave and clear in afterLeave so the expand/collapse animates to the
element's actual height instead of 720px.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/renderer/src/components/WindowSideBar.vue`:
- Line 1: The file WindowSideBar.vue has Prettier formatting failures; run the
formatter (pnpm run format) or directly run Prettier on the WindowSideBar.vue
component to fix spacing/indentation in the <template> block and any other
sections, then stage the formatted changes and push so CI passes.

---

Duplicate comments:
In `@src/renderer/src/components/WindowSideBar.vue`:
- Around line 212-216: The folder Icon currently checks isPinnedSectionCollapsed
but should reflect each group's real collapsed state; replace the binding to use
isGroupCollapsed(group) so the icon stays synchronized with the chevron and
group collapse state (update the Icon :icon prop where isPinnedSectionCollapsed
is referenced inside the WindowSideBar component to call isGroupCollapsed(group)
instead).
- Around line 722-735: The pin toggle races when users click rapidly because
handleTogglePin computes nextPinned from session.isPinned and launches async
work without a serialization guard; fix by checking and setting the single-slot
guard (pinFlightSessionId) at the start of handleTogglePin for the given session
id (early-return if another flight is active for that session), compute
nextPinned after acquiring the guard (or read the latest store/session state
right before calling commitPinToggle/animatePinFlight), then clear the guard in
a finally block; ensure you also avoid stomping pinFeedbackSessionId during an
in-flight toggle and reference the existing functions/refs: handleTogglePin,
commitPinToggle, animatePinFlight, pinFlightSessionId, and pinFeedbackSessionId.
- Around line 804-809: The CSS rule using .sidebar-group-collapse-enter-to and
.sidebar-group-collapse-leave-from with a hard-coded max-height: 720px causes
clipping for tall groups; in WindowSideBar.vue replace this static cap by
removing the fixed max-height and implement Vue transition hooks (e.g.
beforeEnter/enter/afterEnter and beforeLeave/leave/afterLeave) on the transition
wrapping the group: in beforeEnter set el.style.maxHeight = '0px', in enter set
el.style.maxHeight = el.scrollHeight + 'px' and clear it in afterEnter, and for
collapse set el.style.maxHeight = el.scrollHeight + 'px' in beforeLeave then to
'0px' in leave and clear in afterLeave so the expand/collapse animates to the
element's actual height instead of 720px.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 08f5114a-0837-4f14-9e58-568ad84e0fee

📥 Commits

Reviewing files that changed from the base of the PR and between 8c419d1 and faf1690.

📒 Files selected for processing (1)
  • src/renderer/src/components/WindowSideBar.vue

@zhangmo8 zhangmo8 merged commit 5781fa0 into dev Mar 26, 2026
2 of 3 checks passed
@zhangmo8 zhangmo8 deleted the message-item-ux branch March 26, 2026 04:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant