Skip to content

Polish vault inline panel transitions and compact header control#670

Open
busiqiao wants to merge 14 commits intobinaricat:mainfrom
busiqiao:feature/vault-inline-panel-polish
Open

Polish vault inline panel transitions and compact header control#670
busiqiao wants to merge 14 commits intobinaricat:mainfrom
busiqiao:feature/vault-inline-panel-polish

Conversation

@busiqiao
Copy link
Copy Markdown

@busiqiao busiqiao commented Apr 9, 2026

Summary

This PR refines the Hosts/Vault editing experience by making the inline side panel and main content transitions feel more coherent, while keeping the top toolbar usable within the existing minimum window width.

What Changed

  • improved the inline side panel open/close behavior so the panel and main content resize feel more synchronized
  • kept inline panels mounted long enough to animate closed cleanly instead of disappearing abruptly
  • refined the Hosts header so actions stay on a single row and compact smoothly when the side panel opens
  • moved the Connect action into the search input and tuned its styling/behavior for both empty and active states
  • updated host/group/serial inline panel wrappers and related sub-panels so close animations can complete correctly
  • adjusted search placeholder copy to better match the quick-connect flow

Notes

  • no new dependencies were added
  • existing inline editing behavior and minimum window width constraints were preserved
  • the compact header state is intentionally tied to the real side-panel state to avoid desynchronized layout behavior

Testing

  • npm run lint -- --max-warnings=0
  • npm run build

Copilot AI review requested due to automatic review settings April 9, 2026 10:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refines the Vault/Hosts inline editing layout by synchronizing inline aside panel transitions with main content resizing and compacting the Hosts header controls while the side panel is open.

Changes:

  • Reworked inline aside panel animation to use a mounted/visible state with CSS transitions (instead of enter keyframes) for smoother open/close.
  • Updated VaultView Hosts header layout (search + connect action, compact control sizing) and adjusted grid sizing behavior.
  • Plumbed an open prop through host/group/serial detail panels and subpanels to support animated close.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
index.css Replaces keyframe-based inline panel enter animation with data-state-driven width/flex-basis transitions.
components/ui/aside-panel.tsx Adds inline panel presence management and data-state/aria-hidden wiring for inline layout transitions.
components/VaultView.tsx Coordinates Hosts header compaction, host panel deferred presence, and panel open/close state handling.
components/HostDetailsPanel.tsx Adds open prop and forwards it to AsidePanel and nested subpanels.
components/SerialHostDetailsPanel.tsx Adds open prop and forwards it to AsidePanel.
components/GroupDetailsPanel.tsx Adds open prop and forwards it to AsidePanel and nested subpanels.
components/host-details/ProxyPanel.tsx Adds open prop and forwards it to AsidePanel.
components/host-details/EnvVarsPanel.tsx Adds open prop and forwards it to AsidePanel.
components/host-details/CreateGroupPanel.tsx Adds open prop and forwards it to AsidePanel.
components/host-details/ChainPanel.tsx Adds open prop and forwards it to AsidePanel.
application/i18n/locales/en.ts Updates Hosts search placeholder copy for quick-connect flow.
application/i18n/locales/zh-CN.ts Updates Hosts search placeholder copy for quick-connect flow.
.gitignore Ignores .omx/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return () => {
window.clearTimeout(timeoutId);
};
}, [open]);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

The useDeferredPanelPresence effect references INLINE_ASIDE_PANEL_ANIMATION_MS but it isn’t included in the dependency array. With react-hooks/exhaustive-deps enabled (warn) and --max-warnings=0, this will fail lint. Add the constant to the deps array or add a justified eslint-disable-next-line react-hooks/exhaustive-deps for this effect.

Suggested change
}, [open]);
}, [open, INLINE_ASIDE_PANEL_ANIMATION_MS]);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Leaving this as-is.

INLINE_ASIDE_PANEL_ANIMATION_MS is a module-level constant, not a reactive value, so including it in the dependency array is not necessary here. The branch still passes:

  • npm run lint -- --max-warnings=0
  • npm run build

Comment on lines 1309 to 1313
const handleEditGroupConfig = useCallback((groupPath: string) => {
clearPendingHostPanelReset();
setIsHostPanelOpen(false);
setEditingHost(null);
clearHostPanelDraft();
setEditingGroupPath(groupPath);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

handleEditGroupConfig clears editingHost/newHostGroupPath immediately via clearHostPanelDraft(). Because the host panel render condition depends on editingHost, this can unmount the host panel immediately and prevent the close animation/presence logic from running. Consider calling closeHostPanel() (or otherwise deferring draft clearing until after INLINE_ASIDE_PANEL_ANIMATION_MS) before opening the group panel.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in d7c33ae by deferring the host-to-group transition so the host panel can finish closing before the group panel opens.

Comment on lines 2981 to 2986
{/* Group Details Panel */}
{currentSection === "hosts" && isGroupPanelOpen && editingGroupPath && (
<GroupDetailsPanel
open={isGroupPanelOpen}
key={editingGroupPath}
groupPath={editingGroupPath}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

GroupDetailsPanel is conditionally rendered only when isGroupPanelOpen is true, so when it closes it unmounts immediately and the new open prop / AsidePanel close transition can’t run. If you want the close animation to complete, keep the panel mounted while open={false} (e.g., a deferred-presence boolean + delayed clearing of editingGroupPath).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in d7c33ae by keeping the group details panel mounted long enough for the inline close transition to complete, instead of unmounting immediately when isGroupPanelOpen flips to false.

Comment on lines 2133 to 2138
@@ -2019,8 +2134,7 @@
viewMode === "grid"
? cn(
"grid gap-3",
!hasHostsSidePanel && "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
)
)
: "flex flex-col gap-0",
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

This grid className now calls cn("grid gap-3",) with only a single argument and a dangling comma/newline, which looks like an accidental leftover from removing the conditional grid-cols-* classes. Consider simplifying to just "grid gap-3" (and apply the same cleanup to the other similar sections).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Cleaned up in d7c33ae for readability

Comment on lines 2236 to 2241
@@ -2123,8 +2237,7 @@
viewMode === "grid"
? cn(
"grid gap-3",
!hasHostsSidePanel && "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
)
)
: "flex flex-col gap-0",
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

This grid className now calls cn("grid gap-3",) with only a single argument and a dangling comma/newline, which looks like an accidental leftover from removing the conditional grid-cols-* classes. Consider simplifying to just "grid gap-3".

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Cleaned up in d7c33ae for readability

Comment on lines 2339 to 2345
className={cn(
displayedGroups.length === 0 ? "hidden" : "",
viewMode === "grid"
? cn(
"grid gap-3",
!hasHostsSidePanel && "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
)
)
: "flex flex-col gap-0",
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

This grid className now calls cn("grid gap-3",) with only a single argument and a dangling comma/newline, which looks like an accidental leftover from removing the conditional grid-cols-* classes. Consider simplifying to just "grid gap-3".

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Cleaned up in d7c33ae for readability

Comment on lines 2581 to 2586
className={cn(
viewMode === "grid"
? cn(
"grid gap-3",
!hasHostsSidePanel && "grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
)
)
: "flex flex-col gap-0",
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

This grid className now calls cn("grid gap-3",) with only a single argument and a dangling comma/newline. Consider simplifying to just "grid gap-3" for readability.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Cleaned up in d7c33ae for readability

window.clearTimeout(timeoutId);
}
};
}, [isInline, isMounted, open]);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

useInlinePanelPresence references INLINE_ASIDE_PANEL_ANIMATION_MS inside the effect but it isn’t included in the dependency array. With react-hooks/exhaustive-deps enabled and --max-warnings=0, this will fail lint. Add the constant to the deps array or add a justified eslint-disable-next-line react-hooks/exhaustive-deps.

Suggested change
}, [isInline, isMounted, open]);
}, [INLINE_ASIDE_PANEL_ANIMATION_MS, isInline, isMounted, open]);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Leaving this as-is.

INLINE_ASIDE_PANEL_ANIMATION_MS is a module-level constant, not a reactive value, so including it in the dependency array is not necessary here. The branch still passes:

  • npm run lint -- --max-warnings=0
  • npm run build

busiqiao added 13 commits April 15, 2026 01:20
The hosts vault now keeps the inline editor, header controls, and
content width changes on a single visual rhythm so editing feels more
intentional instead of jumpy. The compact header state now preserves
all actions in one row, the search field uses an inline Connect action,
and inline panels stay mounted long enough to animate closed cleanly.

Constraint: Must preserve the existing inline editing workflow and minimum window width
Constraint: No new dependencies or window-manager changes for this pass
Rejected: Keep delayed grid relayout logic | caused visible desync gaps during close
Rejected: Two-row header in compact mode | created unstable layout shifts across panel states
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep header compacting tied to real side-panel state; avoid reintroducing independent delayed layout toggles without visual verification
Tested: npm run lint -- --max-warnings=0
Tested: npm run build
Not-tested: Manual visual QA on native desktop (only WSLg-driven iteration)
The vault editor transitions now keep group details mounted long enough
for close animations to finish, and host-to-group handoff no longer
clears host panel state before the exit motion can run. While touching
that area, the temporary grid class leftovers were simplified for
readability.

Constraint: Must keep the current inline panel animation model and branch scope intact
Rejected: Ignore the review comments | would leave the group panel close animation effectively broken
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep deferred-presence logic paired with deferred draft clearing whenever inline panel visibility depends on draft state
Tested: npm run lint -- --max-warnings=0
Tested: npm run build
Not-tested: Manual visual regression pass after the follow-up fix
The follow-up panel-presence fix referenced group-panel cleanup callbacks
before they were initialized, which caused VaultView to throw during
render and left the hosts page blank. Reordering that cleanup effect and
keeping the related panel-transition fixes intact restores the view while
preserving the intended close-animation behavior.

Constraint: Must preserve the review-follow-up animation fixes while removing the runtime initialization fault
Rejected: Revert the group-panel transition work entirely | would throw away the substantive review fixes unnecessarily
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: In this component, do not add effects that reference callback const bindings before those callbacks are declared
Tested: npm run lint -- --max-warnings=0
Tested: npm run build
Tested: Playwright page load on local Vite dev server
Not-tested: Manual end-to-end interaction pass after this hotfix
Tighten the Vault inline editor sizing and smooth the shared header/
panel behavior so the editor feels more coherent during open/close.

Constraint: Must preserve the current Vault inline editing workflow
Confidence: medium
Scope-risk: narrow
Tested: npm run lint -- --max-warnings=0
Tested: npm run build
Adjust the Vault inline panel width and motion timing to better match
the latest UI tuning, including header compact timing during panel close.

Constraint: Must preserve the current Vault split-style interaction model
Confidence: medium
Scope-risk: narrow
Tested: npm run lint -- --max-warnings=0
Tested: npm run build
The inline vault details panels now avoid double-render overlap during
Host/Group switches while preserving the normal open animation for
non-swap sidebar opens. The host grid freeze behavior also keeps cards
and grouped dividers visually stable during sidebar resizing without
clipping card shadows.

Constraint: Group Details and Host Details share one visual lane but are still separate panel components
Constraint: Sidebar open animation must remain intact for ordinary opens outside detail-panel swaps
Rejected: Always disable initial inline animation | broke normal sidebar pop-out motion
Rejected: Use extra padding/margins during frozen grid resize | caused cards to twitch at animation start
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep swap-specific animation bypass tightly scoped to Group↔Host transitions only
Tested: npx eslint components/VaultView.tsx components/ui/aside-panel.tsx components/GroupDetailsPanel.tsx components/HostDetailsPanel.tsx components/SerialHostDetailsPanel.tsx
Tested: npm run lint
Not-tested: Manual UI verification across all vault view/sort-mode combinations
The hosts header now holds its compact layout steady while the inline
sidebar resize freeze is active, avoiding a second relayout at the
moment the panel animation starts. This keeps the sidebar expansion
smoother without changing the detail-panel swap behavior.

Constraint: Header compactness must still track side-panel presence after the resize settles
Rejected: Leave header compact state fully live during resize | caused a visible hitch at animation start
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Directive: During sidebar freeze windows, avoid introducing new header relayout triggers
Tested: npx eslint components/VaultView.tsx
Tested: npm run lint
Not-tested: Manual performance comparison on low-end hardware
Slow the Vault details panel motion and align the visual layer with the layout shell so opening and closing can be inspected without the old blank-gap artifact. The panel root now owns only width changes while the inner visual layer owns border, background, and shadow timing.

Constraint: User needs a slow-motion build to inspect drawer behavior before further animation work
Rejected: Keep the fast timing while debugging | too hard to observe phase ordering reliably
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: The current 1200ms timing is intentionally slow for observation — retune after motion behavior is confirmed
Tested: eslint on changed panel files; npm run build; manual host/group drawer smoke via preview
Not-tested: host↔group switching and serial edit drawer path
The first attempt to keep cards live during drawer motion used observer-driven relayout compensation and caused disappearing content plus sustained jitter in Hosts. This commit removes that unstable path and returns VaultView to a stable baseline so the non-freezing design can be reintroduced with a safer two-state approach.

Constraint: User explicitly reported continuous jitter and content loss after opening the inline drawer
Rejected: Keep iterating on the observer-driven approach | too unstable to preserve as a baseline
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Reintroduce non-freezing card motion only with bounded state transitions, not continuous resize-driven replay
Tested: npm run build; preview boot smoke; host and group panel open/close smoke after rollback
Not-tested: host↔group switching and serial edit drawer path
The Hosts page drawer animation now keeps the inline panel, host cards, group cards, and related chrome on a shared 1100ms timeline. Card motion uses stable start/end snapshots with unique card IDs so repeated hosts in Recent/Main sections do not borrow the wrong geometry, while already-open Host/Group panel switches stay at full width and only replace content.

Constraint: Right details panels use a 340px inline width and Hosts content padding changes while the panel is active

Rejected: Continuous ResizeObserver-driven animation | previously caused disappearing content and layout thrash

Rejected: Keyframe-driven ghost launch rewrite | regressed the already-stable opening behavior

Confidence: medium

Scope-risk: moderate

Directive: Keep Host/Group same-width switches out of opening/closing phases; only closed-to-open transitions should animate panel width

Tested: npx eslint components/VaultView.tsx components/ui/aside-panel.tsx

Tested: npm run build

Tested: Playwright smoke for Host open/close, Group open/close, and Group Details to Host Details switch

Not-tested: Subjective frame pacing on low-end GPUs
Remove dual width+flex-basis CSS transition that caused transitionend
to fire prematurely when one property completed before the other,
producing a visible snap at the end of the drawer slide-in.

Switch flex-basis to auto so it naturally follows the transitioning
width value at every frame, eliminating the synchronization issue
entirely.
All synchronized timing values updated consistently across CSS
transitions, header compact/expand, content padding, and JS timers.
Extend ghost card measurement system to also capture per-grid heights
for pinned, recent, and groups grids. Apply transitioning fixed
heights to these secondary grids during the sidebar animation so
section titles ("Pinned", "Recently Connected", "Groups", "Hosts")
shift smoothly instead of jumping.
@busiqiao busiqiao force-pushed the feature/vault-inline-panel-polish branch from 6508169 to f71d387 Compare April 14, 2026 17:20
@busiqiao
Copy link
Copy Markdown
Author

@binaricat 点击编辑按钮弹出的右侧侧边栏的弹出和关闭动画我优化了好几版,目前这版感觉还行,辛苦检查一下效果和代码

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.

2 participants