Skip to content
Case study · 04 / 15← All work

Paso

A ticketing platform built from a design handoff, with every screen measured against its mock.

Role
Frontend architecture and full-stack development
Stack
Next.js + React, pnpm + Turborepo, Go, Postgres + Drizzle, Valkey, Playwright + Vitest
Event page
Event pageThe design handoff as a test, not a reference
(01) The problem

Paso is an Argentine ticketing platform for fans, organizers, promoters and door staff. Its promise is trust: every fee shown on its own line, a QR that the door checks without internet, official resale with a price cap, and promoters paid for the sales they bring. The design handoff had 66 artboards and 98 screens. Building them was half the job. Keeping them true to the design while the product changed underneath was the other half.

My roleI built it end to end: both Next.js apps, the shared packages, the Go API and the fidelity pipeline that measures every screen.
(02) ResultsCounted in the code
98screens pixel-diffed against their mock
73screens within 0.5% of the design on the last full run
32end-to-end Playwright tests
(03) Architecture

Three decisions that shaped it.

  1. 01

    The design handoff as a test, not a reference

    Why. A script cuts every artboard into standalone panels. A manifest maps each panel to the route and state that implements it, with query strings that pin things like a running timer. `pnpm visual` shoots the mock and the real route at the artboard's width, runs pixelmatch and writes mock, implementation and diff images. Under 0.5% different, the screen counts as verified. A screen with no route shows up as pending, so the checklist cannot drift.

    Trade-off. Pixel diffs are strict about things users never notice. A breakpoint set just above an artboard's width changed four screens by 5% to 8%. When the product deliberately departs from the mock, the screen is marked as decided, with the reason written down, and still measured.

  2. 02

    Fee rules written twice, held together by golden vectors

    Why. The service fee is 4% per ticket, capped at $400. The screen computes it in a pure TypeScript package. The Go API computes what is actually charged. A script generates golden vectors from the TypeScript rules, including the exact point where the cap takes over, and the Go tests read the same file.

    Trade-off. Two implementations of the same arithmetic. The vectors catch a disagreement only for the cases they list, so every new rule needs new vectors.

  3. 03

    A QR the door can verify without a server

    Why. Venues lose signal right when the line is longest. Each ticket carries an Ed25519 signature and rotates every 30 seconds, so the door app checks it on the phone. Scanned tickets are stored in IndexedDB and shared between tabs over BroadcastChannel. Phones with signal exchange their scans every 20 seconds.

    Trade-off. Two doors with no signal at all can each accept the same ticket once. Closing that gap needs Bluetooth between phones, which is not built yet.

(04) GalleryKeep scrolling →
Event page
Event page · Ticket selection with the final price up front. Verified at 0.113% against its mock.
Settlements
Settlements · The organizer's payout, line by line: sales, resale, promoter commissions and installment costs.
Door scanner
Door scanner · The door app, a separate PWA that validates the signed QR on the phone.
Fidelity diff
Fidelity diff · Mock, implementation and diff for the promoter panel. The mock says $186.500; the data computes 94 × $2.000 = $188.000. The diff shows that and nothing else.
(05) What I'd do next
01Re-run the full fidelity pass. The manifest now marks 37 screens as decided and the last report counts 23, so the published numbers are behind the code.
02Build the Bluetooth fallback so two doors with no signal still see each other's scans.
03Finish moving the remaining domains from mock-api to the Go API, so Go is the only thing that talks to Postgres.
Next project · 05 / 15
Reality Graph →An interactive knowledge graph in 3D, in English and Spanish
Case study · 04 / 15

Paso

A ticketing platform built from a design handoff, with every screen measured against its mock.

Role
Frontend architecture and full-stack development
Stack
Next.js + React, pnpm + Turborepo, Go, Postgres + Drizzle, Valkey, Playwright + Vitest
Paso
(01) The problem

Paso is an Argentine ticketing platform for fans, organizers, promoters and door staff. Its promise is trust: every fee shown on its own line, a QR that the door checks without internet, official resale with a price cap, and promoters paid for the sales they bring. The design handoff had 66 artboards and 98 screens. Building them was half the job. Keeping them true to the design while the product changed underneath was the other half.

My roleI built it end to end: both Next.js apps, the shared packages, the Go API and the fidelity pipeline that measures every screen.
(02) Results
98screens pixel-diffed against their mock
73screens within 0.5% of the design on the last full run
32end-to-end Playwright tests
(03) Architecture

Three decisions that shaped it.

01

The design handoff as a test, not a reference

Why. A script cuts every artboard into standalone panels. A manifest maps each panel to the route and state that implements it, with query strings that pin things like a running timer. `pnpm visual` shoots the mock and the real route at the artboard's width, runs pixelmatch and writes mock, implementation and diff images. Under 0.5% different, the screen counts as verified. A screen with no route shows up as pending, so the checklist cannot drift.

Trade-off. Pixel diffs are strict about things users never notice. A breakpoint set just above an artboard's width changed four screens by 5% to 8%. When the product deliberately departs from the mock, the screen is marked as decided, with the reason written down, and still measured.

02

Fee rules written twice, held together by golden vectors

Why. The service fee is 4% per ticket, capped at $400. The screen computes it in a pure TypeScript package. The Go API computes what is actually charged. A script generates golden vectors from the TypeScript rules, including the exact point where the cap takes over, and the Go tests read the same file.

Trade-off. Two implementations of the same arithmetic. The vectors catch a disagreement only for the cases they list, so every new rule needs new vectors.

03

A QR the door can verify without a server

Why. Venues lose signal right when the line is longest. Each ticket carries an Ed25519 signature and rotates every 30 seconds, so the door app checks it on the phone. Scanned tickets are stored in IndexedDB and shared between tabs over BroadcastChannel. Phones with signal exchange their scans every 20 seconds.

Trade-off. Two doors with no signal at all can each accept the same ticket once. Closing that gap needs Bluetooth between phones, which is not built yet.

(04) Gallery
Event page
Event page · Ticket selection with the final price up front. Verified at 0.113% against its mock.
Settlements
Settlements · The organizer's payout, line by line: sales, resale, promoter commissions and installment costs.
Door scanner
Door scanner · The door app, a separate PWA that validates the signed QR on the phone.
Fidelity diff
Fidelity diff · Mock, implementation and diff for the promoter panel. The mock says $186.500; the data computes 94 × $2.000 = $188.000. The diff shows that and nothing else.
(05) What I'd do next
01Re-run the full fidelity pass. The manifest now marks 37 screens as decided and the last report counts 23, so the published numbers are behind the code.
02Build the Bluetooth fallback so two doors with no signal still see each other's scans.
03Finish moving the remaining domains from mock-api to the Go API, so Go is the only thing that talks to Postgres.
Next projectReality Graph →
Nahuel SantillánEN