# NAN-451 — Financials Tab Flicker Fix

## Status: DONE — PR #390 (DRAFT)

## Commit
- `ef28de32` fix(financials): defer mount via LazyTab to eliminate tab-switch flicker (NAN-451)

## Diagnosis

Checked all 5 hypotheses from the TASK.md:

1. **Re-render loop from setter-during-render** — NOT the cause. No `setX()` at top-level render body in FinancialsContent or its parents.

2. **Layout shift skeleton/loaded height mismatch** — NOT the cause. `FinancialsLoadingSkeleton` is shown while loading, but the real source of the flicker was earlier (before any data fetch).

3. **React Query stale refetch** — NOT the cause. `useFinancialsOverviewViewModel` and `useFinancialStatementViewModel` both use `staleTime: STALE_TIME_6H`, so no refetch-on-focus flicker.

4. **Unstable ref props to collapsible-tab-view** — NOT the primary cause. `TabContent` uses `React.memo` and `useMemo` for the container style.

5. **Skia/Reanimated worklet remount** — NOT the primary cause in isolation.

**ACTUAL ROOT CAUSE**: `StockDetailTabs.tsx` — the `financials` tab was the ONLY heavy tab not wrapped in `LazyTab`:

```tsx
// Before (only financials was unwrapped):
if (activeTab === 'financials') return <FinancialsContentLazy asOf={asOf} ticker={ticker} />;

// Every other heavy tab:
return (
  <LazyTab tabKey={activeTab}>
    <TabComp ticker={ticker} />
  </LazyTab>
);
```

`LazyTab` uses `useDeferredMount` which calls `InteractionManager.runAfterInteractions`. This defers mounting the tab content until after the tab-switch animation completes. Without this deferral, `FinancialsContent` mounted synchronously during the animation, blocking the JS thread and causing the visible flicker.

## Fix

Wrapped `FinancialsContentLazy` in `LazyTab tabKey="financials"`, matching the pattern of all other heavy tabs:

```tsx
if (activeTab === 'financials') {
  return (
    <LazyTab tabKey="financials">
      <FinancialsContentLazy asOf={asOf} ticker={ticker} />
    </LazyTab>
  );
}
```

`LazyTab` shows `TabSkeleton` (200px skeleton box) until `InteractionManager.runAfterInteractions` fires, then unmounts the skeleton and mounts the real content — after the animation has settled. `FinancialsContent` then shows `FinancialsLoadingSkeleton` while data fetches.

## Before/After
- Before: tapping Financials tab caused a 1-2 frame white/blank flash during the tab animation
- After: tab shows skeleton (smooth) → content (after animation settles)
