Skip to content

Fix: misc#19

Closed
petar-omni wants to merge 12 commits intomainfrom
fix/misc
Closed

Fix: misc#19
petar-omni wants to merge 12 commits intomainfrom
fix/misc

Conversation

@petar-omni
Copy link
Copy Markdown
Collaborator

@petar-omni petar-omni commented Feb 6, 2026

This pull request introduces real-time price streaming and integration with Hyperliquid WebSocket APIs, enabling live updates for market and portfolio data. It also adds a new UI component for animated price changes and refactors the codebase to support these enhancements. The most important changes are grouped below:

Hyperliquid WebSocket integration and streaming:

  • Added HyperliquidService to packages/common/src/services/hyperliquid/index.ts for real-time candle and mid-price subscriptions using WebSocket APIs.
  • Introduced midPriceAtom and candleStreamAtom for subscribing to live market data, and exposed these atoms for use throughout the application. [1] [2] [3] [4] [5] [6]

Live market and portfolio updates:

  • Added marketsBySymbolAtom and updateMarketsMidPriceAtom to update market prices in real time based on mid-price streams. [1] [2]
  • Added updatePositionsMidPriceAtom to update portfolio positions with live mark prices, improving accuracy of displayed data. [1] [2] [3]
  • Increased refresh interval for market data to one minute to reduce load and rely more on streaming updates.

UI enhancements:

  • Added new PriceFlash component to animate price changes, and corresponding CSS for up/down flashes. [1] [2] [3]

Codebase and dependency improvements:

  • Refactored position and order form hooks to use records for improved lookup performance and consistency. [1] [2] [3]
  • Updated dependencies and removed unnecessary testing libraries to streamline package configuration. [1] [2] [3] [4]
  • Refactored Vite config to use a factory function for better composability. [1] [2] [3]

Formatting and utility changes:

  • Updated formatTPOrSLSettings to return structured objects instead of strings for easier UI rendering.

These changes collectively enable robust real-time market and portfolio updates, improve UI responsiveness, and streamline the codebase for future enhancements.

Summary by CodeRabbit

Release Notes

  • New Features

    • Price flash animation displaying price movement direction
    • Real-time mid-price updates for markets and positions
    • Market sorting functionality in market selector
    • Integrated TradingView chart component for market analysis
  • Improvements

    • Enhanced market information display with oracle price and open interest calculations
    • Improved market selector with clickable coin symbols for quick switching
    • Better responsive design for mobile devices
    • Refined funding rate and fee information presentation
  • Dependencies

    • Added lightweight-charts and Hyperliquid market data support
    • Updated platform dependencies to latest stable versions

Copilot AI review requested due to automatic review settings February 6, 2026 23:16
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
perps-dashboard Ready Ready Preview, Comment Apr 2, 2026 1:34pm
perps-widget Ready Ready Preview, Comment Apr 2, 2026 1:34pm

Request Review

@socket-security
Copy link
Copy Markdown

socket-security bot commented Feb 6, 2026

Copy link
Copy Markdown

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

This PR adds Hyperliquid WebSocket-based streaming to drive real-time market/portfolio price updates, refactors markets/positions to Record<id, AtomRef<...>> for faster lookups and reactive updates, and introduces a PriceFlash UI helper for animating price changes.

Changes:

  • Add HyperliquidService + atoms for streaming mid prices and candles; wire streaming into market/position state updates.
  • Refactor portfolio/market atoms and multiple UI callsites to use Record + AtomRef instead of arrays.
  • Add PriceFlash component + CSS animation, update UI to use it, and adjust tooling/dependency configuration (Vite config factory, package deps, scripts).

Reviewed changes

Copilot reviewed 36 out of 37 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
pnpm-workspace.yaml Adds Hyperliquid client to the shared catalog; removes testing-library entries.
pnpm-lock.yaml Lockfile updates for new deps and removed testing libs.
package.json Simplifies dev script and normalizes build script placement.
packages/common/package.json Adds Hyperliquid dependency and switches @effect/experimental to catalog spec.
packages/common/vite.config.ts Updates to use the new createCommonViteConfig() factory.
packages/common/src/vite.config.ts Refactors shared Vite config into a factory function.
packages/widget/vite.config.ts Switches to createCommonViteConfig() usage.
packages/dashboard/vite.config.ts Switches to createCommonViteConfig() usage.
packages/widget/package.json Dependency spec changes (notably devDependency removals).
packages/dashboard/package.json Switches @effect/experimental to catalog spec and removes testing-library dev deps.
packages/common/src/styles/base.css Adds keyframes/classes for price flash animations.
packages/common/src/components/molecules/price-flash.tsx New component to animate price changes by toggling CSS classes.
packages/common/src/components/index.ts Exports the new PriceFlash component.
packages/common/src/services/runtime.ts Registers HyperliquidService in the runtime layer.
packages/common/src/services/index.ts Re-exports Hyperliquid service module.
packages/common/src/services/hyperliquid/index.ts New Hyperliquid WS/HTTP service wrapper (subscriptions + snapshot).
packages/common/src/atoms/hyperliquid-atoms.ts Introduces midPriceAtom stream source.
packages/common/src/atoms/candle-atoms.ts Introduces candleStreamAtom family for candle subscriptions.
packages/common/src/atoms/index.ts Exports candle atoms from the common atoms entrypoint.
packages/common/src/atoms/markets-atoms.ts Adds marketsBySymbolAtom, increases refresh interval, adds mid-price market updates.
packages/common/src/atoms/portfolio-atoms.ts Refactors positionsAtom to a Record of AtomRefs; adds mid-price position updates.
packages/common/src/lib/formatting.ts Changes formatTPOrSLSettings to return structured { tp, sl }.
packages/common/src/hooks/use-order-form.ts Updates position lookup logic to use Record.get instead of array search.
packages/dashboard/src/components/modules/root/Preload.tsx Mounts live mid-price update atoms for markets/positions.
packages/widget/src/components/modules/Root/PreloadAtoms.tsx Mounts live mid-price updates for positions when wallet is connected.
packages/dashboard/src/components/modules/trade/market-info/index.tsx Uses PriceFlash, adjusts display fields (incl. OI/funding).
packages/dashboard/src/components/modules/trade/market-info/market-selector-popover.tsx Adds sortable headers and adjusts displayed metrics (incl. OI).
packages/dashboard/src/components/modules/trade/order-form/index.tsx Updates advanced order display; adds maker/taker fee rows.
packages/dashboard/src/components/molecules/positions/index.tsx Enhances tab labels to show counts for positions/orders when connected.
packages/dashboard/src/components/molecules/positions/positions-tab.tsx Refactors to AtomRef-based data flow and adds market selection interaction.
packages/widget/src/components/modules/Home/index.tsx Updates positions count logic for Record-based positions.
packages/widget/src/components/modules/Home/Positions/index.tsx Refactors positions aggregation/rendering for Record of AtomRefs.
packages/widget/src/components/modules/Home/Positions/position-card.tsx Refactors to accept positionRef and read via useAtomRef.
packages/widget/src/components/modules/PositionDetails/Overview/index.tsx Refactors position lookup from array .find to Record.get.
packages/widget/src/components/modules/PositionDetails/Overview/Position/index.tsx Refactors to pass positionRef and read reactively via useAtomRef.
packages/widget/src/components/modules/PositionDetails/Close/state.tsx Refactors close flow to fetch position via Record.get.
packages/widget/src/components/modules/Order/Overview/index.tsx Updates TP/SL settings display to use new structured formatter output.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

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

volume: Order.mapInput(Order.number, (ref: MarketRef) => ref.value.volume24h),
oi: Order.mapInput(
Order.number,
(ref: MarketRef) => ref.value.openInterest * ref.value.markPrice,
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

openInterest is already documented/typed as “total notional value of all open positions” (i.e., already in quote/USD). Multiplying it by markPrice here will inflate the value and break the OI sort. Use openInterest directly for both sorting and display unless the API field semantics have changed (in which case the API schema/docs should be updated too).

Suggested change
(ref: MarketRef) => ref.value.openInterest * ref.value.markPrice,
(ref: MarketRef) => ref.value.openInterest,

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

Choose a reason for hiding this comment

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

hm, from what i see in API responses, it returns openInterest in baseAsset size, while swagger comment says this as total notional value of all open positions - @Philippoes is comment wrong or this in fact should be in notional value?

Copy link
Copy Markdown
Contributor

@Philippoes Philippoes Mar 6, 2026

Choose a reason for hiding this comment

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

We'll fix the swagger comment

</Text>
<Text variant="labelSmWhiteNeg" className="font-medium">
{market.makerFee ? formatRate(market.makerFee) : "-"}
{formatCompactUsdAmount(market.openInterest * market.markPrice)}
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

Open interest is displayed as market.openInterest * market.markPrice, but openInterest is already a notional value per the API schema. This will overstate OI; use market.openInterest directly (or update the API schema/docs if the backend now returns contracts instead of notional).

Suggested change
{formatCompactUsdAmount(market.openInterest * market.markPrice)}
{formatCompactUsdAmount(market.openInterest)}

Copilot uses AI. Check for mistakes.
{/* Open Interest */}
<Text variant="labelSmWhiteNeg" className="font-medium">
{formatCompactUsdAmount(market.openInterest)}
{formatCompactUsdAmount(market.openInterest * market.markPrice)}
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

Open interest display is being computed as market.openInterest * market.markPrice, but openInterest is already a notional value per the API schema. This will show an incorrect (overstated) OI number; display market.openInterest directly unless the backend field meaning has changed.

Suggested change
{formatCompactUsdAmount(market.openInterest * market.markPrice)}
{formatCompactUsdAmount(market.openInterest)}

Copilot uses AI. Check for mistakes.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 13, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4cb1515d-98f2-4171-bbf3-bb0051fdd13a

📥 Commits

Reviewing files that changed from the base of the PR and between 11455f0 and 636d935.

📒 Files selected for processing (21)
  • packages/common/src/atoms/actions-atoms.ts
  • packages/common/src/atoms/hyperliquid-atoms.ts
  • packages/common/src/atoms/index.ts
  • packages/common/src/atoms/order-form-atoms.ts
  • packages/common/src/components/molecules/leverage-dialog.tsx
  • packages/common/src/components/molecules/order-type-dialog.tsx
  • packages/common/src/components/ui/dialog.tsx
  • packages/common/src/domain/signer.ts
  • packages/common/src/domain/wallet.ts
  • packages/common/src/hooks/use-order-form.ts
  • packages/common/src/services/api-client/api-schemas.ts
  • packages/common/src/services/api-client/client-factory.ts
  • packages/common/src/services/hyperliquid/index.ts
  • packages/common/src/services/wallet/browser-signer.ts
  • packages/dashboard/src/components/modules/trade/order-form/index.tsx
  • packages/widget/index.html
  • packages/widget/src/app.tsx
  • packages/widget/src/components/modules/Order/Overview/index.tsx
  • packages/widget/src/components/modules/PositionDetails/Overview/modify-dialog.tsx
  • packages/widget/src/components/modules/Root/PreloadAtoms.tsx
  • packages/widget/src/main.css

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Walkthrough

The PR introduces mid-price WebSocket subscriptions via a new HyperliquidService, adds animated price change visualization (PriceFlash component), refactors position and market state management using AtomRef patterns for improved reactivity, extends the API client with candle data and market detail endpoints, consolidates type definitions (PositionSide, MarginMode, OrderType), updates inter-component data flow patterns, and bumps dependency versions.

Changes

Cohort / File(s) Summary
Root & Dependency Updates
package.json, pnpm-workspace.yaml
Simplified dev script to turbo dev, added top-level build script. Updated catalog dependencies: @effect/experimental to catalog, added @nktkas/hyperliquid and lightweight-charts. Removed @testing-library/* from multiple packages. Bumped TanStack/tooling versions.
HyperliquidService & Atoms
packages/common/src/services/hyperliquid/index.ts, packages/common/src/atoms/hyperliquid-atoms.ts, packages/common/src/atoms/index.ts, packages/common/src/services/index.ts
Introduced HyperliquidService with WebSocket support for candle subscriptions and mid-price streaming via Stream.broadcastDynamic. Exported midPriceAtom wrapping subscribeMidPrice. Re-exported new hyperliquid module in public atoms API.
Market Reactivity
packages/common/src/atoms/markets-atoms.ts, packages/common/src/atoms/portfolio-atoms.ts
Added marketsBySymbolAtom for Record-based market lookups, updateMarketsMidPriceAtom to sync mid-prices to markets, updatePositionsMidPriceAtom for position markPrice updates. Changed refreshMarketsAtom timing from 10s to 1min. Extended positions to use Record<marketId, AtomRef>.
Order & Form Atoms
packages/common/src/atoms/order-form-atoms.ts, packages/common/src/atoms/actions-atoms.ts, packages/common/src/lib/formatting.ts
Updated OrderSide type from PositionDtoSide to PositionSide. Simplified writable atoms with Atom.make. Refactored field access via getFieldAtoms() instead of separate setValue/getFieldValue. Changed formatTPOrSLSettings signature to return {tp, sl} object. Simplified actionAtom to Atom.make.
Form Hooks & Components
packages/common/src/hooks/use-*-form.ts, packages/common/src/components/molecules/limit-price-dialog.tsx, packages/common/src/components/molecules/leverage-dialog.tsx
Updated hook implementations (use-order-form, use-deposit-form, use-withdraw-form) to use getFieldAtoms pattern. Added useHandleLeverageChange hook for leverage updates. Extended LeverageDialog with optional nativeButton prop. Updated PositionDtoSide to PositionSide throughout.
Price Visualization
packages/common/src/components/molecules/price-flash.tsx, packages/common/src/styles/base.css, packages/common/src/components/index.ts
Introduced PriceFlash component that animates price changes with directional CSS classes (price-flash-up/-down). Added corresponding @keyframes and animation utilities. Re-exported from public components API.
Vite Configuration
packages/common/src/vite.config.ts, packages/common/vite.config.ts, packages/dashboard/vite.config.ts, packages/widget/vite.config.ts
Changed commonViteConfig from constant to factory function createCommonViteConfig(). Updated all consuming packages to invoke the factory on import.
Runtime & Services
packages/common/src/services/runtime.ts, packages/common/src/domain/signer.ts, packages/common/src/domain/wallet.ts, packages/common/src/services/wallet/browser-signer.ts
Extended runtime with ManagedRuntime and HyperliquidService.Default via Match-based signer selection. Widened signTransaction error type to include SwitchChainError. Updated SwitchChain error handling in BrowserSignerLayer.
API Client Schemas
packages/common/src/services/api-client/api-schemas.ts, packages/common/src/services/api-client/client-factory.ts
Major schema extensions: added PerpActionTypes, PositionSide, MarginMode, OrderType, ActionStatus. Introduced candle/OHLCV support (CandleDto, CandlesResponseDto, MarketsControllerGetCandles). Added MarketDetailDto with chart config. Extended ActionDto with ActionSummaryDto. Replaced transaction type/status literals with PerpTransactionType/PerpTransactionStatus. Added MarketsControllerGetMarketById endpoint. ~300 LOC additions.
Dashboard Chart & Market Components
packages/dashboard/src/components/modules/trade/chart-v2.tsx, packages/dashboard/src/components/modules/trade/market-info/index.tsx, packages/dashboard/src/components/modules/trade/market-info/market-selector-popover.tsx
Introduced ChartV2 component with TradingView widget integration and stub datafeed. Updated market-info to use PriceFlash, conditional funding colors, conditional fee displays. Added sortable table behavior to market-selector-popover with column sort state and toggle handlers. Updated market calculations to use markPrice × openInterest.
Dashboard Order & Position Components
packages/dashboard/src/components/modules/trade/order-form/index.tsx, packages/dashboard/src/atoms/selected-market-atom.ts, packages/dashboard/src/components/modules/trade/market-selector-popover.tsx, packages/dashboard/src/components/molecules/header/deposit/state.tsx, packages/dashboard/src/components/molecules/header/withdraw/state.tsx, packages/dashboard/src/components/modules/root/Preload.tsx
Updated order form to use useHandleLeverageChange and new formatTPOrSLSettings return shape. Refactored initMarketAtom to Effect.fn with generator syntax. Updated selected-market-atom initialization. Refactored deposit/withdraw form state to use getFieldAtoms pattern. Mounted updateMarketsMidPriceAtom and updatePositionsMidPriceAtom preloads.
Dashboard Positions & Tab Components
packages/dashboard/src/components/modules/trade/market-info/market-selector-popover.tsx, packages/dashboard/src/components/molecules/positions/index.tsx, packages/dashboard/src/components/molecules/positions/positions-tab.tsx
Refactored PositionWithMarket to use AtomRef and AtomRef instead of raw DTOs. Updated PositionRow to derive values via useAtomRef. Introduced dynamic tab labels with position/order counts. Integrated market selection via selectedMarketAtom.
Widget Position & Order Components
packages/widget/src/components/modules/Home/Positions/index.tsx, packages/widget/src/components/modules/Home/Positions/position-card.tsx, packages/widget/src/components/modules/Home/index.tsx, packages/widget/src/components/modules/Order/Overview/index.tsx, packages/widget/src/components/modules/PositionDetails/Overview/Position/index.tsx, packages/widget/src/components/modules/PositionDetails/Overview/index.tsx, packages/widget/src/components/modules/PositionDetails/Overview/modify-dialog.tsx, packages/widget/src/components/modules/PositionDetails/Close/state.tsx, packages/widget/src/components/modules/Root/PreloadAtoms.tsx
Refactored positions to use AtomRef pattern throughout. Updated PositionCard and PositionCardContent signatures to accept positionRef instead of position. Updated PositionDtoSide to PositionSide. Extended leverage handling with useHandleLeverageChange. Updated Record-based position lookups via Record.get + Option utilities. Mounted mid-price update atoms.
Widget Styling & Metadata
packages/widget/src/app.tsx, packages/widget/src/main.css, packages/widget/index.html
Changed root container width to responsive w-full sm:w-[400px]. Removed TRADING_VIEW_WIDGET_SCRIPT_URL preload. Updated #app padding from 60px to 30px with mobile breakpoint (8px at <400px). Added body margin reset and background. Added mobile-specific meta tags (mobile-web-app-capable, status-bar-style).
UI Component Refinements
packages/common/src/components/ui/dialog.tsx, packages/common/src/components/molecules/order-type-dialog.tsx
Updated dialog popup variants to include responsive width w-[calc(100vw-2rem)]. Adjusted Content padding from pb-5 pt-6 px-6 to pb-5 p-6. Removed explicit padding from OrderTypeDialog.Content.

Sequence Diagram(s)

sequenceDiagram
    participant App as App / Preload
    participant HS as HyperliquidService
    participant MPA as midPriceAtom
    participant UMMPA as updateMarketsMidPriceAtom
    participant MA as marketsBySymbolAtom
    participant Markets as Markets Record
    participant PF as PriceFlash Component

    App->>HS: Connect WebSocket
    HS->>HS: Create WebSocket transport
    
    App->>MPA: Subscribe to mid-price stream
    MPA->>HS: Call subscribeMidPrice()
    HS->>HS: Subscribe to allMids WebSocket
    
    HS-->>MPA: Stream<AllMidsWsEvent>
    MPA-->>App: midPriceAtom value updates
    
    App->>UMMPA: Mount updateMarketsMidPriceAtom
    UMMPA->>MPA: Read latest mids
    UMMPA->>MA: Get markets by symbol
    MA->>Markets: Transform Record<id, ref> to Record<symbol, ref>
    
    UMMPA->>Markets: Update each market.markPrice from mids
    Markets-->>PF: Market price change
    
    PF->>PF: Compare previous vs current price
    PF->>PF: Add price-flash-up/down class
    PF-->>App: Render animated price with CSS
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Poem

🐰 Whiskers twitching with delight,
Mid-prices flow through WebSocket's light,
Markets flash in green and red,
Position atoms dance ahead,
Charts and sorts make trades sublime,
Reactive dreams in record time!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Fix: misc' is vague and generic, using non-descriptive terminology that does not convey meaningful information about the substantial changeset. Replace with a more descriptive title that captures the primary changes, such as 'feat: add Hyperliquid streaming and real-time price updates' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/misc

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

Copy link
Copy Markdown

@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: 5

Caution

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

⚠️ Outside diff range comments (2)
packages/dashboard/src/components/modules/trade/market-info/market-selector-popover.tsx (1)

343-345: ⚠️ Potential issue | 🟡 Minor

Consider defensive handling for potential index mismatch.

Option.getOrThrow will throw if the index is out of bounds. While the virtualizer's count is based on marketData.length, there's a small window where data could change between renders (e.g., rapid search input), leaving the virtualizer with stale indices.

🛡️ Suggested defensive approach
             {rowVirtualizer.getVirtualItems().map((virtualItem) => {
-              const market = _Array
+              const marketOption = _Array
                 .get(marketData, virtualItem.index)
-                .pipe(Option.getOrThrow);
+
+              if (Option.isNone(marketOption)) return null;
+
+              const market = marketOption.value;

               return (
packages/dashboard/src/components/modules/trade/order-form/index.tsx (1)

401-414: ⚠️ Potential issue | 🟡 Minor

Only the last OrderDetailRow should set isLast.

isLast is now set on multiple rows, which will render extra bottom borders. Keep it on the final row only.

🧩 Suggested fix
         <OrderDetailRow
           label="Fees"
           value={formatAmount(calculations.fees)}
-          isLast
         />
         <OrderDetailRow
           label="Taker Fee"
           value={market.takerFee ? formatRate(market.takerFee) : "-"}
-          isLast
         />
         <OrderDetailRow
           label="Maker Fee"
           value={market.makerFee ? formatRate(market.makerFee) : "-"}
           isLast
         />
🤖 Fix all issues with AI agents
In `@packages/common/src/hooks/use-order-form.ts`:
- Around line 88-95: Result.getOrElse is being passed a non-lazy fallback
(Record.empty) causing a type mismatch; update the call where positions is
defined (the useAtomValue of positionsAtom) to pass a lazy fallback by wrapping
Record.empty in a zero-arg function (i.e., use Result.getOrElse(() =>
Record.empty)). This change affects the positions declaration using
Result.getOrElse, and no other logic (like Record.get(positions, marketId) or
currentPosition) needs modification.

In `@packages/common/src/lib/formatting.ts`:
- Around line 142-158: The doc and output are inconsistent: update
formatTPOrSLSettings to accept a side parameter (e.g., side: PositionSide) and
apply the correct sign prefix when composing TP/SL strings; inside
formatTPOrSLSettings use getTpSlPercentPrefix(side, "tp") and
getTpSlPercentPrefix(side, "sl") (or equivalent prefix helpers) and concatenate
that prefix with formatPercentage(percentage) when mapping takeProfit/stopLoss,
preserving the "TP Off"/"SL Off" fallback behavior; keep the Option/pipe logic
but replace the current unsigned formatPercentage call with prefix +
formatPercentage so short positions render signed percentages as documented.

In `@packages/common/src/services/hyperliquid/index.ts`:
- Around line 13-18: The WebSocketTransport instance created in the scoped
Effect (inside scoped: Effect.gen) is never closed; add a scope finalizer to
call transport.close() when the service scope ends. Inside the generator where
you construct const transport = new WebSocketTransport(), register a finalizer
(e.g., via Effect.addFinalizer or the equivalent used in this codebase) that
invokes transport.close() (wrapped in an Effect.sync/Effect.try if necessary) so
the transport is cleaned up when the scoped Effect completes; leave
HttpTransport and existing subscription unsubscribe finalizers unchanged.

In `@packages/dashboard/src/components/modules/trade/market-info/index.tsx`:
- Line 138: The displayed open interest is incorrectly multiplied by
market.markPrice; change the call that currently uses
formatCompactUsdAmount(market.openInterest * market.markPrice) to use the raw
USD value formatCompactUsdAmount(market.openInterest) instead, removing the
multiplication by market.markPrice so the UI uses the API's already-denominated
USD openInterest (refer to formatCompactUsdAmount and the market.openInterest /
market.markPrice symbols to locate the line).

In `@packages/widget/src/components/modules/Home/Positions/index.tsx`:
- Around line 42-49: The totals calculation uses AtomRef.value directly
(positionRefs.reduce accessing ref.value.unrealizedPnl and ref.value.margin),
which does not subscribe to updates and will not re-render when individual
position atoms change; to fix, subscribe to each position AtomRef before
reducing (e.g., call useAtomRef or the library's hook on each ref or map
positions to subscribed values) so the component re-renders on per-position
updates, or alternatively compute totals from a derived/computed atom that
itself subscribes to the underlying position atoms; update the code around
positionRefs / positions to obtain subscribed position values rather than
reading ref.value directly.
🧹 Nitpick comments (7)
packages/dashboard/src/components/modules/trade/market-info/market-selector-popover.tsx (1)

171-188: Consider adding aria-sort for improved accessibility.

The sortable header buttons work well visually. For screen reader users, adding an aria-sort attribute would communicate the current sort state more explicitly.

♿ Optional accessibility improvement
   return (
     <button
       type="button"
       onClick={() => onToggle(column)}
       className="flex items-center gap-1 hover:text-white/80 transition-colors"
+      aria-sort={direction === "asc" ? "ascending" : direction === "desc" ? "descending" : undefined}
     >
packages/common/src/styles/base.css (1)

26-52: Add a reduced‑motion fallback for price flash animations.

A short override helps users who prefer reduced motion avoid flashing effects.

♿ Suggested CSS override
+@media (prefers-reduced-motion: reduce) {
+  .price-flash-up,
+  .price-flash-down {
+    animation: none;
+  }
+}
packages/common/src/services/hyperliquid/index.ts (1)

26-28: Consider using Effect.mapError for error transformation.

Using Effect.catchAll with an error class constructor works, but Effect.mapError is more idiomatic for simple error type transformations since it clearly signals the intent is to transform the error rather than handle and recover from it.

♻️ Proposed refactor
       const candleSnapshot = (params: {
         coin: typeof CoinSchema.Type;
         interval: typeof CandleIntervalSchema.Type;
         startTime: number;
         endTime?: number;
       }) =>
         Effect.tryPromise(() => infoClient.candleSnapshot(params)).pipe(
-          Effect.catchAll((cause) => new GetCandleSnapshotError({ cause })),
+          Effect.mapError((cause) => new GetCandleSnapshotError({ cause })),
         );
packages/common/src/atoms/portfolio-atoms.ts (1)

116-137: Consider adding a brief comment clarifying the double .value access.

Line 128 uses marketRef.value.value.id which is correct but may confuse readers:

  • First .value unwraps the Option from Record.get
  • Second .value accesses the AtomRef's current value

A brief inline comment would improve maintainability.

📝 Suggested documentation
         Record.toEntries(mids).forEach(([symbol, price]) => {
           const marketRef = Record.get(markets, symbol);
           if (marketRef._tag === "None") return;
 
-          const positionRef = Record.get(positions, marketRef.value.value.id);
+          // marketRef.value is the AtomRef, .value.id accesses the market's id
+          const positionRef = Record.get(positions, marketRef.value.value.id);
           if (positionRef._tag === "None") return;
packages/dashboard/src/components/modules/trade/chart-v2.tsx (2)

230-233: Consider showing an error state when TradingView library fails to load.

When createChartWidget returns null (TradingView not loaded), the code sets isLoading to false, leaving the user with an empty chart area. Consider showing an error or retry state instead.

💡 Suggested improvement
+  const [error, setError] = useState<string | null>(null);
+
   useEffect(() => {
     const container = containerRef.current;
     if (!container) return;
 
     // Clean up a previous widget instance
     if (widgetRef.current) {
       widgetRef.current.remove?.();
       widgetRef.current = null;
     }
 
     setIsLoading(true);
+    setError(null);
 
     const widget = createChartWidget(container, symbol);
     widgetRef.current = widget;
 
     if (widget) {
       widget.onChartReady?.(() => {
         setIsLoading(false);
       });
     } else {
       // TradingView library not loaded yet – keep loading state
       setIsLoading(false);
+      setError("Chart library not available");
     }

249-258: Consider extracting the inline loading overlay to reuse ChartLoading.

The loading overlay (lines 249-258) duplicates the ChartLoading component defined at lines 173-182. Consider reusing it for consistency.

♻️ Suggested refactor
       {isLoading && (
-        <div className="absolute inset-0 bg-black flex items-center justify-center z-10 transition-opacity duration-200">
-          <div className="flex flex-col items-center gap-3">
-            <div className="w-8 h-8 border-2 border-white/20 border-t-white rounded-full animate-spin" />
-            <Text as="span" variant="inherit" className="text-white/70 text-sm">
-              Loading chart...
-            </Text>
-          </div>
-        </div>
+        <div className="absolute inset-0 z-10">
+          <ChartLoading />
+        </div>
       )}
packages/dashboard/src/components/molecules/positions/positions-tab.tsx (1)

151-153: Accessing positionRef.value outside of useAtomRef is safe here but warrants a note.

Line 152 accesses positionRef.value.marketId directly for filtering, which doesn't subscribe to updates. This is acceptable since marketId is immutable for a position, but differs from the reactive pattern used in PositionRow.

Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (3)
packages/common/src/services/hyperliquid/index.ts (1)

48-60: Consider bounded capacity for the mid-price broadcast.

Stream.broadcastDynamic({ capacity: "unbounded" }) can accumulate messages without limit if consumers lag behind. For high-frequency mid-price updates, a bounded capacity with a dropping strategy (e.g., { capacity: 16, strategy: "dropping" }) may prevent unbounded memory growth while still delivering timely prices.

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

In `@packages/common/src/services/hyperliquid/index.ts` around lines 48 - 60,
subscribeMidPrice currently uses Stream.broadcastDynamic({ capacity: "unbounded"
}) which can lead to unbounded memory growth when consumers lag; change the
broadcast configuration in the subscribeMidPrice Stream to use a bounded
capacity and dropping strategy (for example, Stream.broadcastDynamic({ capacity:
16, strategy: "dropping" })) so slow consumers won’t cause unbounded buffering;
update the call site where subscribeMidPrice is defined to pass the bounded
config to Stream.broadcastDynamic and ensure any tests/consumers tolerate
dropped mid-price events.
packages/common/src/components/molecules/Chart/constants.ts (1)

28-31: Consider dynamic precision for the price formatter.

The maximumFractionDigits: 2 may truncate precision for low-value assets (e.g., 0.000012340.00). The formatAmount function already has smart precision logic when custom digits aren't specified—removing the maximumFractionDigits override would enable adaptive formatting based on price magnitude.

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

In `@packages/common/src/components/molecules/Chart/constants.ts` around lines 28
- 31, The priceFormatter in localization currently forces maximumFractionDigits:
2 which can truncate low-value asset prices; remove the explicit
maximumFractionDigits override so formatAmount can use its built-in adaptive
precision logic. Update the localization.priceFormatter to call
formatAmount(price, { symbol: null }) (i.e., drop maximumFractionDigits) so
formatAmount can determine precision dynamically.
packages/common/src/components/molecules/Chart/index.tsx (1)

65-68: Potential repeated setContainer calls on re-renders.

The ref callback runs on every render. Since setContainer(Result.success(el)) is called each time with the same element, this may trigger unnecessary atom updates. Consider guarding against redundant updates:

♻️ Suggested improvement
         ref={(el) => {
           if (!el) return;
+          if (Result.isSuccess(containerResult) && containerResult.value === el) return;
           setContainer(Result.success(el));
         }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/src/components/molecules/Chart/index.tsx` around lines 65 -
68, The ref callback in the Chart component currently calls
setContainer(Result.success(el)) on every render which can cause redundant atom
updates; change the ref handler to first read the current container (or use the
atom's getter) and only call setContainer(Result.success(el)) when el is
non-null and not equal to the previously stored element (e.g., when container is
not a success or container.value !== el), so you avoid setting the same Result
repeatedly from the ref callback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/common/src/components/molecules/Chart/state.ts`:
- Line 82: chartDataAtom is reading interval once with
ctx.get(chartIntervalAtom) but ChartDataParams keys exclude interval so changing
the toggle doesn't recreate the atom and the chart keeps old candles; fix by
including interval in the atom's identity or subscribing to changes: either add
an interval field to ChartDataParams and use it in the atom key (so
chartDataAtom is keyed by symbol, variant, and interval), or replace
ctx.get(chartIntervalAtom) with ctx.subscribe(chartIntervalAtom) inside the
chartDataAtom initializer so the atom re-runs when chartIntervalAtom changes;
update all references to ChartDataParams and the chartDataAtom initializer
accordingly.

---

Duplicate comments:
In `@packages/common/src/services/hyperliquid/index.ts`:
- Around line 13-18: The WebSocketTransport created inside the scoped Effect (in
scoped: Effect.gen(function* () { ... })) is never closed; add a finalizer that
calls transport.close() when the service scope ends. Inside the Effect.gen block
after creating const transport = new WebSocketTransport(), register a scope
finalizer (using the environment/scope helper available in your Effect
framework) to invoke transport.close() so the SubscriptionClient/WebSocket is
cleaned up when the scoped service is released.

---

Nitpick comments:
In `@packages/common/src/components/molecules/Chart/constants.ts`:
- Around line 28-31: The priceFormatter in localization currently forces
maximumFractionDigits: 2 which can truncate low-value asset prices; remove the
explicit maximumFractionDigits override so formatAmount can use its built-in
adaptive precision logic. Update the localization.priceFormatter to call
formatAmount(price, { symbol: null }) (i.e., drop maximumFractionDigits) so
formatAmount can determine precision dynamically.

In `@packages/common/src/components/molecules/Chart/index.tsx`:
- Around line 65-68: The ref callback in the Chart component currently calls
setContainer(Result.success(el)) on every render which can cause redundant atom
updates; change the ref handler to first read the current container (or use the
atom's getter) and only call setContainer(Result.success(el)) when el is
non-null and not equal to the previously stored element (e.g., when container is
not a success or container.value !== el), so you avoid setting the same Result
repeatedly from the ref callback.

In `@packages/common/src/services/hyperliquid/index.ts`:
- Around line 48-60: subscribeMidPrice currently uses Stream.broadcastDynamic({
capacity: "unbounded" }) which can lead to unbounded memory growth when
consumers lag; change the broadcast configuration in the subscribeMidPrice
Stream to use a bounded capacity and dropping strategy (for example,
Stream.broadcastDynamic({ capacity: 16, strategy: "dropping" })) so slow
consumers won’t cause unbounded buffering; update the call site where
subscribeMidPrice is defined to pass the bounded config to
Stream.broadcastDynamic and ensure any tests/consumers tolerate dropped
mid-price events.

const { chart, series } = yield* ctx.result(initChartAtom);
const timeScale = chart.timeScale();

const interval = ctx.get(chartIntervalAtom);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the state.ts file around line 82
cd packages/common/src/components/molecules/Chart && wc -l state.ts && sed -n '70,100p' state.ts

Repository: stakekit/perps-widget

Length of output: 1019


🏁 Script executed:

# Check ChartDataParams definition
rg -n "ChartDataParams|interface.*Params" packages/common/src/components/molecules/Chart/state.ts -B 2 -A 5

Repository: stakekit/perps-widget

Length of output: 1034


🏁 Script executed:

# Check how chartIntervalAtom is used and if there are dependency mechanisms
rg -n "chartIntervalAtom|subscribe" packages/common/src/components/molecules/Chart/state.ts -B 2 -A 2

Repository: stakekit/perps-widget

Length of output: 876


🏁 Script executed:

# Look for atom re-run or dependency patterns across the file
rg -n "ctx\.subscribe|ctx\.get\(" packages/common/src/components/molecules/Chart/state.ts -B 1 -A 1

Repository: stakekit/perps-widget

Length of output: 128


🏁 Script executed:

# Look for how chartIntervalAtom is updated (setInterval or similar patterns)
rg -rn "chartIntervalAtom\s*=" packages/common/src/components/molecules/Chart/ -A 2 -B 2

Repository: stakekit/perps-widget

Length of output: 451


🏁 Script executed:

# Check the full file to understand the atom lifecycle and any re-initialization
sed -n '142,160p' packages/common/src/components/molecules/Chart/state.ts

Repository: stakekit/perps-widget

Length of output: 557


🏁 Script executed:

# Search for usage of chartDataAtom and interval changes in Chart components
rg -rn "chartDataAtom|setInterval" packages/common/src/components/molecules/Chart/ --type ts

Repository: stakekit/perps-widget

Length of output: 543


🏁 Script executed:

# Check if there's a hook or effect that handles interval changes
rg -rn "useAtom|useAtomValue|interval" packages/common/src/components/molecules/Chart/ --type ts | head -40

Repository: stakekit/perps-widget

Length of output: 1716


🏁 Script executed:

# Let's check the index.tsx to see how chartDataAtom is used when interval changes
sed -n '1,80p' packages/common/src/components/molecules/Chart/index.tsx | grep -A 20 -B 5 "chartData\|setInterval"

Repository: stakekit/perps-widget

Length of output: 1703


🏁 Script executed:

# Check if there's any re-initialization logic or dependency tracking in the file
rg -n "addFinalizer|ctx.subscribe|ctx.get" packages/common/src/components/molecules/Chart/state.ts -B 1 -A 1

Repository: stakekit/perps-widget

Length of output: 303


🏁 Script executed:

# Let's see the complete chartDataAtom definition to understand its full lifecycle
sed -n '73,155p' packages/common/src/components/molecules/Chart/state.ts

Repository: stakekit/perps-widget

Length of output: 2582


Interval changes don't update the chart—old interval data persists.

ctx.get(chartIntervalAtom) reads the interval once at atom initialization. Since chartDataAtom is keyed only by symbol and variant (not interval), changing the interval via the toggle won't re-initialize the atom. The chart will continue showing data and subscribing to candles for the old interval.

Fix by adding interval to ChartDataParams so each interval gets its own atom instance, or use ctx.subscribe(chartIntervalAtom) to re-run the atom when the interval changes.

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

In `@packages/common/src/components/molecules/Chart/state.ts` at line 82,
chartDataAtom is reading interval once with ctx.get(chartIntervalAtom) but
ChartDataParams keys exclude interval so changing the toggle doesn't recreate
the atom and the chart keeps old candles; fix by including interval in the
atom's identity or subscribing to changes: either add an interval field to
ChartDataParams and use it in the atom key (so chartDataAtom is keyed by symbol,
variant, and interval), or replace ctx.get(chartIntervalAtom) with
ctx.subscribe(chartIntervalAtom) inside the chartDataAtom initializer so the
atom re-runs when chartIntervalAtom changes; update all references to
ChartDataParams and the chartDataAtom initializer accordingly.

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.

3 participants