feat(design-library): migrate ResizablePanel + upgrade Storybook to 10.4.0#31073
Conversation
…Storybook to 10.4.0 Migrates the horizontal split-view component from web/src/components/app/core/ResizablePanel/ in vellum-assistant-platform to the design library as a reusable UI primitive. Convention compliance: - Kebab-case file naming (resizable-panel.tsx) - No 'use client' directive - Extends ComponentProps<'div'> with data-slot attribute - Uses design library cn() utility and design token CSS variables - Storybook story with three variants (default, custom min widths, narrow) Also upgrades Storybook from 10.3.6 to 10.4.0 (minor version bump). Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Addresses Devin Review findings: - Clamp stored width against minLeftWidth during initialization - Add initial clamp in useEffect for full bounds check after mount - Import Meta/StoryObj from direct dep @storybook/react-vite, not transitive @storybook/react Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
There was a problem hiding this comment.
✦ APPROVE
Value: Migrates ResizablePanel from the platform repo into the design library as a reusable primitive, eliminating the need for PR #31067 (Home page port) to bake its own version. Also bumps Storybook to 10.4.0 with no breaking changes.
What this does:
-
ResizablePanel component — Clean horizontal split-view with a draggable divider. Uses the modern
pointer eventsAPI (onPointerDown/Move/Up) withsetPointerCapture/releasePointerCapturefor reliable cross-browser drag tracking (handles both mouse and touch in one event model). No external resizable library dependency. -
React 19 conventions — fully compliant:
- Function declaration (
export function ResizablePanel) ComponentProps<"div">with rest props spreaddata-slot="resizable-panel"on the root elementcn()utility for conditional class composition- No
forwardRef, nouse clientdirective (Vite SPA) - Named export only
- Function declaration (
-
Drag state management — correct patterns:
dragRef(mutable ref) stores{ startX, startWidth }— values that don't need to trigger re-renders during the drag operationisDragging(React state) drives the visual feedback (opacity transition on the handle)clamphelper is memoized withuseCallbackand bound tominLeftWidth/minRightWidthdepsleftWidthstate is updated during drag viahandlePointerMove, and persisted tolocalStorageonhandlePointerUp
-
Persistence — privacy-safe localStorage:
- Both read (initializer) and write (pointerUp) are wrapped in
try/catch— handles strict-privacy browsing contexts and quota exceeded errors gracefully storageKeyis optional; when absent, component usesdefaultLeftWidthonlyNumber.isFinite(parsed)guards against corrupted stored values
- Both read (initializer) and write (pointerUp) are wrapped in
-
Resize handling —
useEffectwith proper cleanup:window.addEventListener("resize", onResize)with matchingremoveEventListenerin cleanup- The resize handler re-clamps the current width to ensure it stays within bounds when the container shrinks
- This is the right tool for the job here — the effect needs access to the
containerRefand the memoizedclampfunction, which are not easily externalized to module scope foruseSyncExternalStore
-
Accessibility:
- Divider has
role="separator"andaria-orientation="vertical" cursor-col-resizegives the correct affordanceselect-noneduring drag prevents text selection
- Divider has
-
Storybook stories — Three variants: default (300px left), custom min widths (200/150px), narrow default (200px). All use CSS custom properties (
var(--surface-base),var(--surface-lift),var(--content-default)) for theming. Layout wrapper ensures 400px height in the story canvas. -
Storybook upgrade (10.3.6 → 10.4.0) — Minor bump across all four packages (
storybook,@storybook/react-vite,@storybook/addon-docs,@storybook/addon-a11y). PR body links to the 10.4.0 changelog confirming no breaking changes. -
Barrel export — Added to
index.tsfollowing the same pattern as other components (export { ResizablePanel, type ResizablePanelProps }).
Minor / non-blocking:
-
Future enhancement (not blocking): Consider adding
onPointerCancelhandling alongsideonPointerUp. If the user starts a drag and then the browser fires apointercancelevent (e.g., system interrupt, touch-action conflict), the drag state would remain stuck asisDragging = trueanddragRef.currentwould stay non-null. The visual feedback (opacity) would be stuck. AddingonPointerCancel={handlePointerUp}would be a one-line safety net. -
ResizeObserver vs. window.resize: The current approach listens on
window.resizeand clamps based oncontainer.offsetWidth. If the container resizes for reasons other than window resize (e.g., a sidebar collapsing elsewhere in the layout), the clamp won't fire. A future iteration could useResizeObserveron the container itself, but that's a genuine enhancement, not a bug in this PR.
Bot findings:
Devin has auto-monitoring enabled but no substantive review comments yet. No Codex review visible.
CI: All passing — Lint, Type Check, Test, OpenAPI Spec Check, FlexFrame Lint: all success. macOS Tests/Build skipped (expected for a design library PR with no native code).
Vellum Constitution — Inviting: reusable primitives in the design library keep the web app codebase clean and make the platform feel cohesive.
Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
1501d2c
Test Results — ResizablePanel Migration + Storybook 10.4.0Ran Storybook locally on the PR branch, tested all ResizablePanel stories across all three themes.
Evidence screenshotsNotes
|
…l copy Replaces the local apps/web/src/components/resizable-panel.tsx with the design library version at @vellum/design-library/components/resizable-panel (landed in PR #31073). Deletes the now-redundant local file. Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
…eyAPI client (#31067) * feat(web): port Home page from platform repo, wire up React Query + HeyAPI client Part of LUM-1601 - Wire up QueryClientProvider in main.tsx with configured QueryClient - Add HeyAPI client singleton (src/lib/api-client.ts) for daemon endpoints - Add API error utilities (src/lib/api-errors.ts) - Port avatar system: types, API, SVG compositor, useAssistantAvatar hook, AnimatedAvatar + ChatAvatar components - Port Home domain: types, API layer, feed utils, useHomeFeedQuery + useHomeStateQuery hooks, all page components (greeting header, suggestion pills, feed list, filter bar, recap rows, detail panel) - Port shared components: ResizablePanel, useIsMobile hook - Enhance design library Button: add outlined variant, compact size, leftIcon/rightIcon/iconOnly props with proper Tailwind styling - Add /home route in routes.tsx - All files follow new repo conventions: kebab-case filenames, no 'use client' directives, no Next.js imports, .js extension imports Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> * fix: resolve cross-package type resolution for design library file: dep - Add preserveSymlinks to tsconfig.json and vite.config.ts so TypeScript resolves design library imports from the symlink path (apps/web/node_modules/) instead of the real path (packages/design-library/), ensuring a single copy of @types/react is used across both packages. - Add design library runtime deps (@radix-ui/react-slot, class-variance-authority, clsx, tailwind-merge) to apps/web dependencies since bun does not install file: dependency transitive deps in the linked package's node_modules. - Fix SVG path number regex in animated-avatar.tsx to handle compact SVG notation (numbers starting with . like .5 meaning 0.5). Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> * fix: add postinstall script to deduplicate react types for local dev When the design library's devDependencies are installed (e.g. for Storybook), its node_modules/react shadows the web app's copy, causing dual-type errors. The postinstall removes the design library's react copies so TypeScript resolves from the web app's single copy via preserveSymlinks. Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> * fix(web): replace shared mountedRef with per-effect cancellation in AnimatedAvatar Each blink/twitch useEffect now uses a local `let cancelled = false` flag instead of a shared mountedRef. This prevents orphaned inner timeouts from creating duplicate animation chains when isStreaming toggles rapidly — the previous shared ref could be reset to true by a new effect setup while orphaned callbacks from the old effect's cleanup phase were still pending. Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> * fix(web): replace window.location.href with React Router useNavigate in home routes Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> * refactor(web): use ResizablePanel from design library instead of local copy Replaces the local apps/web/src/components/resizable-panel.tsx with the design library version at @vellum/design-library/components/resizable-panel (landed in PR #31073). Deletes the now-redundant local file. Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Prompt / plan
Migrates
ResizablePanelfrom the platform repo'sweb/src/components/app/core/ResizablePanel/to the design library so PR #31067 (Home page port) can import it from@vellum/design-library/components/resizable-panelinstead of baking it in as an app-level component.Also upgrades Storybook from 10.3.6 → 10.4.0 (minor version bump, prompted by Storybook's own upgrade notice).
Why needed
ResizablePanelis a generic horizontal split-view with a draggable divider — it's a reusable UI primitive that belongs in the design library alongside Button, Card, etc. PR #31067 currently duplicates this component atapps/web/src/components/resizable-panel.tsx; with this PR merged, #31067 can consume it from the design library instead.Convention compliance
resizable-panel.tsx)"use client"directive (not needed in Vite SPA)ComponentProps<"div">with rest props spread anddata-slotattributecn()utility and design token CSS variables (--border-base,--content-tertiary)index.tsand accessible via@vellum/design-library/components/resizable-panelsubpathStorybook upgrade
Minor bump from 10.3.6 → 10.4.0 across all four packages (
storybook,@storybook/react-vite,@storybook/addon-docs,@storybook/addon-a11y). No breaking changes in the 10.4.0 changelog.Safety
Test plan
bunx tsc --noEmit— passes (zero type errors)Link to Devin session: https://app.devin.ai/sessions/86b00b0278b64800b3d9ee83cbc8a109
Requested by: @ashleeradka