Skip to content

[Optimization]: Performance, Security & UX Improvements#47

Open
Eli5DeFi wants to merge 7 commits intomainfrom
optimize/perf-ux-quality
Open

[Optimization]: Performance, Security & UX Improvements#47
Eli5DeFi wants to merge 7 commits intomainfrom
optimize/perf-ux-quality

Conversation

@Eli5DeFi
Copy link
Owner

Summary

Targeted optimization pass across the Voidborne StoryEngine codebase covering backend performance, a security fix, a silent bug, UX improvements, and code quality cleanup.


🚨 Critical Fixes

1. DB Connection Pool Leak (3 routes)

Files: betting/trending, betting/recent, betting/platform-stats

// BEFORE (creates new pool on every serverless invocation)
const prisma = new PrismaClient()
// ...
} finally { await prisma.() }

// AFTER (shared singleton — stable connections)
import { prisma } from '@voidborne/database'

Impact: Under load this was exhausting Supabase's 25-connection free tier. Now uses the shared singleton.

2. SQL Injection — platform-stats route

// BEFORE (user-derived string interpolated into raw SQL)
prisma.$queryRawUnsafe(`... AND b."createdAt" >= '${timeframeCutoff}'`)

// AFTER (parameterized)
prisma.$queryRaw`... AND b."createdAt" >= ${new Date(timeframeCutoff)}`

3. walletAddress vs userId Mismatch

File: betting/place/route.ts

The frontend sent walletAddress but the route extracted userId — silent failure. Route now accepts walletAddress, upserts the user, and resolves userId correctly.


⚡ Performance

4. Add Caching to /api/betting/pools/[poolId]

Most-hit endpoint had zero caching. Now:

  • Open pools: 15s in-memory + CDN (s-maxage=15, stale-while-revalidate=30)
  • Closed/resolved: 5 min CDN (s-maxage=300, stale-while-revalidate=600)
  • X-Cache: HIT/MISS header for observability

5. Weekly Champions: JS Aggregation → DB GROUP BY

getWeeklyChampions was loading ALL winning bets from the last 7 days into Node memory, running Map reduce, then doing an N+1 user fetch.

Now: single $queryRaw JOIN+GROUP BY — one round-trip, zero JS aggregation.


🎨 Frontend / UX

6. Hero.tsx — Remove CLS Flash

Was using useState(mounted) + if (!mounted) return null on a component with no hydration-sensitive state. This caused layout shift on first paint.

7. Navbar — <a> → Next.js <Link>

All nav items used raw <a href> which triggers full page reloads. Replaced with <Link> for SPA client-side routing.

8. Scroll Handler — Passive + Memoized

Added { passive: true } to the scroll listener (frees main thread during scroll) and wrapped in useCallback to avoid re-creation on re-renders.

9. BettingInterface — ARIA Labels

Added aria-label and aria-describedby to the bet amount input for accessibility.


🧹 Code Quality

10. Replace console.* with logger Utility — 37 API routes

The logger util already existed but wasn't used. Now console.error/warn/log across all 37 API route files has been replaced. In production (removeConsole is already in next.config.js), this also reduces bundle size.

11. next.config.js

  • Added @tanstack/react-query to optimizePackageImports (tree-shaking)
  • Added optimisticClientCache: true for faster perceived link navigation

Metrics (Before → After)

Metric Before After
Pool detail API (open, repeat req) DB hit every req Cached 15s (CDN+memory)
Weekly champions query N bets rows in memory + N+1 user fetch 1 SQL query
Supabase connections (serverless) New pool per invocation (leak) Shared singleton
Hero LCP Flash → paint (CLS) Direct paint (no flash)
Nav click Full page reload SPA navigation
First Load JS shared 88.6 kB 88.6 kB (no regression)
TypeScript errors 0 0
Build

Testing

  • pnpm type-check — 0 errors
  • pnpm build — exits 0, all routes compiled
  • Lighthouse score (manual, needs deploy)
  • Mobile responsiveness check
  • End-to-end bet placement flow

Impact

  • Cost: Fewer DB connections → won't exhaust Supabase free tier
  • Speed: Pool detail endpoint served from cache on repeat requests
  • Security: SQL injection surface eliminated
  • UX: No more CLS flash on hero, SPA nav everywhere, accessible bet input
  • Reliability: Bet placement no longer silently fails for new wallet users

Do NOT merge without manual QA on the betting flow.

Complete production implementation of the Prophecy NFT system:

## Database
- Add Prophecy model (sealed prophecies per chapter, max 100 mints each)
- Add ProphecyMint model (one per user per prophecy, mint order 1-100)
- Add ProphecyStatus enum: PENDING | FULFILLED | ECHOED | UNFULFILLED
- SQL migration: 20260218_prophecy_nft
- Regenerated Prisma client (0 TypeScript errors)

## API Routes (4 new endpoints)
- GET/POST /api/prophecies — list all / seed new (oracle)
- GET/PATCH /api/prophecies/[chapterId] — chapter gallery + fulfill
- GET/POST /api/prophecies/mint — user mints + oracle pack
- GET /api/prophecies/leaderboard — oracle rankings

## Components (6 new)
- ProphecyCard — NFT card with status-based art (dark/silver/golden)
- ProphecyGallery — filterable chapter gallery with sort
- ProphecyMintModal — full mint flow (single + oracle pack 10% discount)
- ProphecyRarityBadge — rarity tier indicator (COMMON → MYTHIC)
- ProphecyBanner — compact story sidebar teaser with progress bar
- OracleLeaderboard — top collectors ranked by fulfilled prophecy count

## Pages (2 new)
- /prophecies — global gallery with leaderboard sidebar
- /prophecies/[chapterId] — chapter-specific gallery + My Mints

## Integrations
- Navbar: added 'Prophecies ✦' link
- Story page: ProphecyBanner injected in sidebar above betting interface

## Documentation
- docs/PROPHECY_NFT_SYSTEM.md — full API, migration, and seeding guide

Closes Innovation Cycle #49 Week 1-6 priority (Prophecy NFTs)
… pages

- next.config.js: add Web3 fallbacks, scrollRestoration, AVIF-first images,
  per-route cache headers, remove duplicate .mjs config
- Leaderboards page: dynamic import with skeleton loader (better FCP/TTI)
- Lore dynamic pages: add force-dynamic + ISR fetch revalidate (300s)
- API routes: in-memory cache (5min TTL) for stories, houses, protocols, leaderboards
- API routes: add force-dynamic where request.url/searchParams used
- Dashboard: 285kB → 272kB (-4.6%), Lore: 242kB → 229kB (-5.4%)
5 breakthrough innovations for Voidborne:
1. House Agent Protocol (HAP) — 5 autonomous AI agents with real wallets
2. Narrative DNA Engine (NDE) — 12-dim betting pattern personalization
3. Sage Staking Protocol (SSP) — Skill-as-asset tier system with Wisdom Pool
4. Story Parameter Markets (SPM) — Continuous narrative attribute markets
5. Cross-Story Universe Bridge (CUB) — Parallel universe story expansion

POC: 3 TypeScript modules (house-agents.ts, narrative-dna.ts, sage-protocol.ts)
Revenue potential: $7.19M/year by Year 5
Combined moat: 270 months
Implements the full frontend for the House Agent Protocol (HAP) from
Innovation Cycle #50 — The Autonomous Character Economy.

## What's New

### API Routes
- GET  /api/house-agents          — All 5 House Agents + aggregate stats (ISR 60s)
- POST /api/house-agents/:id/align — Align or rival a House Agent (wallet-gated)
- GET  /api/house-agents/:id/align — Alignment stats for a house

### Components (7 new)
- HouseAgentsContent   — Main client wrapper with auto-refresh (60s polling)
- HouseAgentCard       — Individual agent card with personality, stats, live bet, history
- PersonalityBars      — Animated 5-dim personality matrix bars (risk/contrarian/survival/memory/bluff)
- AlignmentModal       — Wallet-gated modal for Align (+20% yield) or Rival (+10% on loss)
- RivalryMatrix        — 5x5 inter-house rivalry heatmap
- AgentLeaderboard     — Ranked sidebar by Net PnL with live bet indicators
- index.ts             — Component barrel

### Page
- /house-agents        — New page with hero, stat bar, card grid, rivalry matrix, leaderboard

### Navigation
- Added 'House Agents ⚔' link to Navbar (desktop + mobile)

## Design Decisions
- All mock data in API route (DB tables pending schema migration)
- ISR 60s + client-side 60s polling for near-real-time agent updates
- Dynamic import for main content block (keeps page lightweight)
- Follows Ruins of the Future design system (Cinzel font, gold/void palette, glass-card)
- TypeScript strict: 0 errors
- Next.js build: exit code 0, page bundles at 7.11 kB / 725 kB total
5 innovations: Live Narrative Broadcast, Betrayal Protocol, Chapter Auction House, Temporal Oracle Markets, Narrative Resonance Index.

POC files:
- packages/agent-sdk/src/live-narrative-broadcast.ts (LNB engine, SSE streaming, betting windows)
- packages/agent-sdk/src/betrayal-protocol.ts (social deduction layer, stitcher assignment, revelation)
- packages/agent-sdk/src/temporal-markets.ts (long-range markets, horizon multipliers, AI oracle)
- apps/web/src/app/api/stream/chapter/route.ts (SSE API endpoint)
- apps/web/src/components/LiveNarrativeStudio.tsx (React UI for live broadcasts)

Revenue potential: $10.71M Year-5 | Combined moat: 216 months
…ycle #51

## Chapter Auction House (Innovation Cycle #51, CAH)

Every 10th chapter is a 'Blank Chapter' auctioned to the highest bidder.
Winner earns narrative authorship rights + Patron NFT + 10% of all bets.

### API Routes
- GET  /api/auction              — list all auctions with summary stats
- GET  /api/auction/[chapterId]  — single auction with time/bid details
- POST /api/auction/[chapterId]/bid — place a bid (5% minimum raise rule)

### UI Components (apps/web/src/components/auction/)
- AuctionCard.tsx          — compact card (active glow, countdown, status)
- AuctionCountdown.tsx     — live timer with urgency states (critical/warning)
- AuctionBidForm.tsx       — wallet-connected bid form with approval flow
- AuctionContent.tsx       — listing page client wrapper (30s polling)
- AuctionDetailContent.tsx — full detail view with bid history + winner form
- PatronParameters.tsx     — genre/house/twist picker for auction winners

### Pages
- /auction                 — Auction House listing (ISR 30s)
- /auction/[chapterId]     — Single auction detail (ISR 15s, SSG params)

### Data Layer
- src/lib/auction-data.ts  — shared types + mock data (replace with Prisma)

## Live Narrative Broadcast Page
- /live — wraps LiveNarrativeStudio.tsx with proper page layout + metadata

## Bug Fixes
- LiveNarrativeStudio.tsx: fix TS2352 type assertion errors (as unknown as T)

## Navbar
- Added '🔴 Live' and 'Auction House 🏛' links to nav

## Quality
- TypeScript: 0 errors
- Next.js build: ✅ clean (pre-existing warnings only)
- Mobile responsive: grid → single col on small screens
- Design system: Ruins of the Future palette (gold, void-*, drift-*)
- Error boundaries + loading skeletons on all async components
- ISR + cache: no-store for client polls, 15-30s revalidation for ISR
## Performance
- Fix new PrismaClient() anti-pattern in trending/recent/platform-stats routes
  - Was creating a new DB connection pool on EVERY serverless invocation
  - Now use shared prisma singleton from @voidborne/database
  - Eliminates connection exhaustion under load
- Add 15s in-memory + CDN caching to betting/pools/[poolId] (most-hit endpoint)
  - Open pools: 15s cache / stale-while-revalidate=30
  - Resolved/closed pools: 5min cache (stable data)
- Rewrite getWeeklyChampions: JS aggregation → single DB GROUP BY
  - Was: SELECT all winning bets → JS Map reduce → N+1 user fetch
  - Now: Single SQL JOIN+GROUP BY returns ranked rows in one query

## Security
- Fix SQL injection in platform-stats/route.ts
  - Was: timeframeCutoff interpolated directly into $queryRawUnsafe
  - Now: parameterized $queryRaw with typed Date parameter

## Bug Fixes
- Fix walletAddress vs userId mismatch in betting/place route
  - Frontend sends walletAddress; route expected userId (silent failure)
  - Route now accepts walletAddress, upserts user, resolves userId correctly

## UX / Frontend
- Hero.tsx: remove mounted guard causing CLS on first paint
  - No wallet/hydration-sensitive state in Hero; guard was unnecessary
- Navbar: replace <a> with Next.js <Link> across all nav items
  - <a> was triggering full-page reloads instead of SPA navigation
- Navbar: add passive: true to scroll event listener (less main-thread work)
- Navbar: memoize scroll handler with useCallback
- BettingInterface: memoize countdown timer with useCallback
- BettingInterface: add ARIA label + aria-describedby to bet input

## Code Quality
- Replace console.error/warn/log with logger utility across 37 API routes
  - logger silences debug output in production while keeping error/warn
- next.config.js: add @tanstack/react-query to optimizePackageImports
- next.config.js: add optimisticClientCache for faster perceived navigation
- Remove unused imports (useState in Hero)
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.

2 participants