DEV Community

medhaholla7
medhaholla7

Posted on

The bug that only existed on an iPhone

The bug that only existed on an iPhone

I built DataLens as a side project: upload a CSV, get back summary statistics, distribution charts, a categorical breakdown and a correlation matrix. React and TypeScript on the front, FastAPI on the back, Recharts doing the drawing.

It worked. I had used it on my own laptop dozens of times. Then I wrote tests, and found out it only worked on my laptop.

Testing four browsers when you own one

I develop on Windows, which means no Safari. That is a problem, because Safari's engine is WebKit and WebKit does not always agree with Chromium about how a page should be laid out.

Playwright solves this. It ships real Chromium, Firefox and WebKit builds and drives all three from one config:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  use: { baseURL: 'http://localhost:4173' },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox',  use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit',   use: { ...devices['Desktop Safari'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
  ],
});
Enter fullscreen mode Exit fullscreen mode

Three tests: does the page render, do the charts appear after analysis, and does anything overflow horizontally. That last one is a single line of DOM measurement:

const overflow = await page.evaluate(() =>
  document.documentElement.scrollWidth > document.documentElement.clientWidth
);
expect(overflow).toBe(false);
Enter fullscreen mode Exit fullscreen mode

If the document is wider than the viewport, the user gets a sideways scrollbar. On mobile that feels broken even when nothing is technically wrong.

The first failure was not a browser problem

All four browsers failed the chart test immediately. That is a useful signal: when everything fails identically, it is not a compatibility issue.

The error context Playwright captured showed the answer in the rendered page:

- text: Failed to load sample data.
Enter fullscreen mode Exit fullscreen mode

My FastAPI CORS config allowed localhost:3000 and localhost:5173. The Vite dev server runs on 5173, so I had never hit this. But vite preview, which serves the actual production build, runs on 4173. Every browser was being refused by my own backend.

One line:

allow_origins=[
    "http://localhost:3000",
    "http://localhost:5173",
    "http://localhost:4173",
]
Enter fullscreen mode Exit fullscreen mode

Worth saying plainly: I would not have found this by clicking around in dev mode, because in dev mode the bug does not exist. It only appeared once I tested what I would actually ship.

The real one: mobile Safari, and nothing else

With CORS fixed, eleven of twelve tests passed. The twelfth:

[mobile-safari] › no layout overflow
Expected: false
Received: true
Enter fullscreen mode Exit fullscreen mode

Chromium fine. Firefox fine. Desktop WebKit fine. Only the iPhone 13 viewport, 390 pixels wide, had horizontal overflow.

The cause was my correlation matrix. It is a table whose column count depends on how many numeric columns the uploaded CSV has, so its natural width is unbounded. On any desktop viewport there is room. At 390 pixels there is not, and the table pushed the whole document wider than the screen.

The fix was responsive CSS, not JavaScript:

@media (max-width: 480px) {
  .stats-row   { grid-template-columns: repeat(2, 1fr); }
  .charts-grid { grid-template-columns: 1fr; }
  .summary-table { font-size: 0.75rem; }
}

html, body { overflow-x: hidden; max-width: 100%; }
.table-wrapper { max-width: 100%; -webkit-overflow-scrolling: touch; }
.dashboard, .upload-card { max-width: 100%; box-sizing: border-box; }
Enter fullscreen mode Exit fullscreen mode

Collapse the four-column stat row to two. Stack the charts. Shrink the table text and let its wrapper scroll independently instead of stretching the page. Twelve of twelve.

While I was there: the 601 kB problem

Vite had been warning me on every build and I had been ignoring it:

(!) Some chunks are larger than 500 kB after minification.
dist/assets/index-DS_AtZhv.js   601.34 kB │ gzip: 182.04 kB
Enter fullscreen mode Exit fullscreen mode

Everything was in one file, including Recharts, which is by far the heaviest dependency. That meant a visitor who opened the page and did nothing still downloaded the entire charting library.

But the dashboard does not render until you click analyse. So it does not need to be in the initial bundle:

const AnalysisDashboard = lazy(() => import("./components/AnalysisDashboard"));

// ...

<Suspense fallback={<p>Loading dashboard...</p>}>
  <AnalysisDashboard analysis={analysis} filename={filename} />
</Suspense>
Enter fullscreen mode Exit fullscreen mode

Rebuild:

dist/assets/index-DYJUX-3l.js              241.42 kB │ gzip:  79.11 kB
dist/assets/AnalysisDashboard-CN02Wouk.js  360.92 kB │ gzip: 103.95 kB
Enter fullscreen mode Exit fullscreen mode

Initial download went from 601 kB to 241 kB, or 182 kB to 79 kB gzipped. A 57% reduction on first load, for four lines of code. The dashboard chunk arrives only when someone actually asks for a chart.

What I would keep doing

Measure the production build, not the dev server. My first Lighthouse run scored the app at 48 with a 12 MB payload. That was Vite serving unminified source with hot reload attached — a number that describes nothing anyone will ever experience. The production build scores 100. If I had put the 48 anywhere public I would have been describing a problem that did not exist.

Write the overflow test. It is three lines and it catches an entire category of layout bug that is invisible on the machine you develop on.

Let the tooling tell you. Vite had been printing the bundle warning for weeks. Playwright found the CORS bug in its first run. Neither required insight, only paying attention.


DataLens is on GitHub: github.com/medhaholla7/datalens

Top comments (0)