Guidance for AI coding agents working in the Eventide (rubbin-hood) codebase.
npm run dev # Start dev server (http://localhost:3000)
npm run build # Production build (Next.js static export)
npm run lint # ESLint (Next.js core-web-vitals + TypeScript)
npx tsc --noEmit # Type-check only
# Testing (Vitest, Node environment)
npm test # Watch mode
npm run test:run # Single run, all tests
npm run test:coverage # With v8 coverage
npx vitest run tests/unit/parsing/section2.test.ts # Single file
npx vitest run -t "should handle empty section" # Single test by nameLocal-first prediction market analytics. No server, no API, no auth. PDF statements parsed client-side, stored in IndexedDB via wa-sqlite.
PDF Upload → pdf.js → Section Parsers → wa-sqlite/IndexedDB
↓
React UI ← TanStack Query ← SQL queries
↑
Zustand (filters, UI state, preferences)
| Layer | Location | Purpose |
|---|---|---|
| Database | src/lib/db/ |
wa-sqlite singleton, query queue, schema, migrations |
| Queries | src/lib/db/queries/ |
Parameterized SQL, sort field whitelisting, barrel-exported |
| Parsing | src/lib/parsing/ |
Pure TS (no React deps), versioned parsers, section logic |
| State | src/lib/state/ |
TanStack Query v5 + Zustand stores |
| Hooks | src/hooks/ |
TanStack Query wrappers over query functions |
| Calculations | src/lib/calculations/ |
FIFO P&L, fee attribution, validation — pure TS |
| Features | src/features/ |
Page-level orchestration: upload flow, analytics view, demo, settings |
| Format | src/lib/format.ts |
formatCurrency, formatPnl, formatPercent |
| Components | src/components/<domain>/ |
Domain folders with barrel index.ts |
| Pages | src/app/(app)/ |
All "use client", routes: dashboard/trades/analytics/settings/upload/demo-parse |
- P&L is computed in SQL — never in JavaScript. See
PNL_EXPRESSION/STATUS_EXPRESSIONinqueries/trades.ts. - Position journal groups by symbol — the
/tradespage showsPositionJournalRow(one row per symbol), not individual YES/NO trades. P&L sourced fromclosed_positionstable viaLEFT JOIN. Child trades lazy-loaded on expand viagetTradesForPosition(symbol). - Upload supports multiple PDFs —
FileUploaderacceptsFile[],upload-flow.tsxparses sequentially with aggregate preview, imports sequentially with per-file duplicate handling and resume. - Chromium-only (Chrome 119+, Edge 119+, Brave 1.60+). OPFS requirement blocks Firefox/Safari.
BrowserBlockerenforces this. - All queries are serialized through the db query queue. Transactions bypass the queue to avoid deadlock.
- Parser registration uses side-effect imports —
src/lib/parsing/register.tsimports platform importers for their registration side effects. Upload flow importsregister.tsto activate dispatch. - Page boundary isolation — PDF items on different pages are NEVER grouped into the same row. This is enforced in
pdf-loader.tsandcolumns.ts. - Robinhood sells = opposite-side buys — Robinhood represents "selling" as "buying the opposite side" (YES+NO=$1.00). Pairing logic in
import-pipeline.tshandles this. - Kalshi settlement_price placement —
settlement_pricemust be placed on the OPEN trade so SQL P&L computes against the entry price. Seekalshi/transform.ts. - FIFO ordering — Within the same date, OPEN trades must be processed before CLOSE/SETTLE. See
calculations/fifo.ts.
- Double quotes for all strings (
"not 'single'") - 2-space indentation, semicolons always, trailing commas
- No Prettier — formatting conventions are by example
- ESLint:
eslint-config-next/core-web-vitals+eslint-config-next/typescript
Order with blank lines between groups:
// 1. React / framework
import * as React from "react";
import { useMemo } from "react";
// 2. External libraries
import { useQuery } from "@tanstack/react-query";
import { create } from "zustand";
// 3. Internal (use @/ alias, never relative outside current module)
import { queryKeys } from "@/lib/state/query-client";
import { getTradesForJournal } from "@/lib/db/queries/trades";
import { cn } from "@/lib/utils";
// 4. Type-only imports last
import type { SortOptions, TradeFilter } from "@/lib/db/types";| What | Convention | Example |
|---|---|---|
| Files | kebab-case | use-trades-data.ts, query-client.ts |
| Components | PascalCase | Tile, TileHeader, BrowserBlocker |
| Hooks | camelCase, use prefix |
useTradesData, useFilterStore |
| Functions | camelCase | getTradesForJournal, createTrade |
| Types/Interfaces | PascalCase | TradeFilter, PaginationOptions |
| Type aliases/unions | PascalCase | TradeSide, UploadStatus |
| SQL constants | UPPER_SNAKE_CASE | PNL_EXPRESSION, STATUS_EXPRESSION |
| Store defaults | camelCase | defaultFilterState, defaultUploadState |
- Base types match SQL schema →
lib/db/types.ts - Separated into: base (DB rows), enriched (computed fields), input (mutations), filter, view
interfacefor object shapes;typefor unions and aliases- SQL nullable →
| null; optional inputs →?: - No
as any,@ts-ignore, or@ts-expect-error
- Named exports only (no default exports except config files like
next.config.ts) - Every module folder has a barrel
index.ts - Export types alongside values:
export { Tile }; export type { TileProps }; - Component domain barrels explicitly name exports (not
export *for component folders)
/** File-level JSDoc block at top of every file */
/** Function-level JSDoc for public functions */
// Inline comments for non-obvious logic only
// ============================================================================
// SECTION SEPARATORS for type/query groupings
// ============================================================================React.forwardReffor base UI primitives; set.displayName- Props extend HTML attributes:
interface XProps extends React.HTMLAttributes<HTMLDivElement> cn()(clsx + tailwind-merge) for class composition"use client"at top of hooks and page components- Financial numbers:
font-mono tabular-nums - Colors: CSS variables
--profit(green),--loss(red),--warning(yellow) in oklch
- Interface → default state →
create<Interface>()pattern persistmiddleware withcreateJSONStorage(() => localStorage)for durable statepartializeto persist only specific fields- Every filter setter resets
pageto 1
- Factory-pattern query keys:
queryKeys.trades.list({ filter, page, pageSize, sort }),queryKeys.positions.journal.list(...),queryKeys.positions.journal.trades(symbol) - 60s default stale time; 5min gc time; child trade queries use 5min stale time
- Hooks in
src/hooks/wrap query functions — never callquery()directly from components
- Parameterized SQL with
?placeholders — never interpolate values - Whitelist sort fields to prevent SQL injection:
allowedFields.includes(field) ? field : "trade_date" query<T>()for reads,execute()for writes,getDatabase()for bulk/direct access- Bulk inserts: caller wraps in
transaction()if needed
- Vitest with Node environment; test files in
tests/unit/<domain>/ - Coverage targets:
src/lib/parsing/**andsrc/lib/calculations/** - Fixtures in
tests/fixtures/; mock factories prefixedcreateMock* - Pattern:
describe("functionName", () => { it("should ...", () => { expect(...) }) }) - Import from vitest:
import { describe, it, expect } from "vitest" tests/setup.tsmockspdfjs-distandpdf-loaderfor Node compatibility
- Write parameterized SQL in
src/lib/db/queries/<domain>.ts - Whitelist sort fields, use
?params - Export from
src/lib/db/queries/index.ts - Create TanStack Query hook in
src/hooks/use-<domain>-data.ts - Add query key to
queryKeysfactory insrc/lib/state/query-client.ts - Export hook from
src/hooks/index.ts
- Add state + setter to
FilterStateinsrc/lib/state/stores.ts - Setter must reset
pageto 1 - Subscribe in the relevant hook; include in query key
- Create in
src/components/<domain>/ - Export from domain barrel
index.ts - Import via folder path:
from "@/components/dashboard"
- Create section parser in
src/lib/parsing/sections/ - Pure TypeScript — no React dependencies
- Add tests in
tests/unit/parsing/ - Register in
src/lib/parsing/registry.ts
Files >500 lines (modify with care):
| File | Lines | Purpose |
|---|---|---|
lib/parsing/robinhood/import-pipeline.ts |
1074 | Full import orchestration |
lib/parsing/robinhood/sections/columns.ts |
981 | Column calibration for PDF tables |
features/imports/upload-flow.tsx |
716 | Upload UI state machine |
lib/parsing/symbol.ts |
593 | Symbol parsing + categorization |
lib/db/queries/trades.ts |
588 | Trade queries with SQL P&L |