{
  "_comment": "AI-readable summary of the SlabTrack ecosystem. Fetched by Claude/Cursor when starting work on the codebase. Update when significant decisions are made.",
  "version": "1.0",
  "lastUpdated": "2026-05-04",
  "repos": {
    "slabtrack": {
      "path": "~/OneDrive/Desktop/GitHub/slabtrack",
      "branch": "main",
      "stack": "Express 5 + React 19 (Vite) + PostgreSQL",
      "role": "Operator brain — collection, scanners, pricing, strategist, command bridge",
      "domain": "slabtrack.io",
      "stripe": null,
      "shippo": null
    },
    "thisorthat": {
      "path": "~/OneDrive/Desktop/GitHub/thisorthat",
      "branch": "master",
      "stack": "Next.js 16 + Postgres + Prisma",
      "role": "Marketplace — Flip and Pull games",
      "domain": "thisorthat.slabtrack.io",
      "stripe": "direct charges on platform, manual capture, idempotency keys present",
      "shippo": "single global ship-from from SHIP_FROM_* env vars"
    },
    "slabtrack-storefront": {
      "path": "~/OneDrive/Desktop/GitHub/slabtrack-storefront",
      "branch": "master",
      "stack": "Next.js 16 + Postgres + Prisma",
      "role": "Multi-tenant vendor storefronts",
      "domain": "storefront.slabtrack.io/[slug]",
      "stripe": "Stripe Connect Express, automatic capture, sale-lock based, idempotency keys present",
      "shippo": "per-shop ship-from from Storefront row"
    }
  },
  "shared_secrets_must_match": [
    "JWT_SECRET",
    "ECOSYSTEM_WEBHOOK_SECRET"
  ],
  "channels": [
    {"slug": "personal", "exclusive": true, "satellite": null},
    {"slug": "thisorthat", "exclusive": false, "satellite": "thisorthat.slabtrack.io"},
    {"slug": "storefront", "exclusive": false, "satellite": "storefront.slabtrack.io"},
    {"slug": "showcase", "exclusive": true, "satellite": "breaks.slabtrack.io"},
    {"slug": "repack", "exclusive": true, "satellite": null},
    {"slug": "ebay", "exclusive": false, "satellite": "ebay.com"},
    {"slug": "whatnot", "exclusive": false, "satellite": "whatnot.com"}
  ],
  "production_critical_files": [
    "thisorthat/src/app/api/webhook/ingest/route.ts",
    "thisorthat/src/app/api/stripe/webhook/route.ts",
    "slabtrack-storefront/src/app/api/stripe/webhook/route.ts",
    "slabtrack/backend/services/sale-lock-service.js",
    "slabtrack/backend/routes/curator.routes.js",
    "slabtrack-storefront/src/lib/storefront-theme.ts"
  ],
  "hard_rules": [
    "Never touch app code when only docs were requested",
    "Never commit real secrets (only sk_test_/pk_test_/whsec_ placeholders in .env.example)",
    "Never use --no-verify or --no-gpg-sign on commits",
    "Always npx tsc --noEmit -p . on TypeScript repos before commit",
    "Always use the Stripe singleton (getStripe() from src/lib/stripe.ts)",
    "Always idempotency-key Stripe creates (checkout.sessions, refunds, paymentIntents)",
    "Always verify Stripe webhook signatures (constructEvent with STRIPE_WEBHOOK_SECRET)",
    "Always include Co-Authored-By trailer on commits"
  ],
  "stripe": {
    "api_version_pinned": "2026-04-22.dahlia",
    "thisorthat_singleton": "thisorthat/src/lib/stripe.ts (getStripe())",
    "storefront_singleton": "slabtrack-storefront/src/lib/stripe.ts",
    "default_spread": 0.25,
    "thisorthat_capture": "manual",
    "storefront_capture": "automatic",
    "platform_app_fee_bps": 300
  },
  "shippo": {
    "env_var_canonical": "SHIPPO_API_TOKEN",
    "env_var_legacy_fallback": "SHIPPO_API_KEY",
    "thisorthat_ship_from_source": "env vars (SHIP_FROM_NAME, SHIP_FROM_STREET, SHIP_FROM_CITY, SHIP_FROM_STATE, SHIP_FROM_ZIP, SHIP_FROM_PHONE, ADMIN_EMAIL)",
    "storefront_ship_from_source": "Storefront DB row (shipFromName, shipFromStreet1, etc.)",
    "default_parcel": "6x4x1.5 inches, 4 oz (one slabbed card)"
  },
  "games": {
    "flip": {
      "logic": "thisorthat/src/lib/games/flip.ts",
      "endpoint": "thisorthat/src/app/api/play/flip/route.ts",
      "outcome_formula": "serverSeed[31] % 2 === 0 ? WIN : LOSE",
      "spread_default": 0.25,
      "win_price": "cardValue * (1 - spread)",
      "lose_price": "cardValue * (1 + spread)"
    },
    "pull": {
      "logic": "thisorthat/src/lib/games/pull.ts",
      "endpoint": "thisorthat/src/app/api/play/pull/route.ts",
      "stack_size_min": 2,
      "stack_size_max": 5,
      "stack_avg_floor": 15,
      "outcome_formula": "serverSeed[0] % stackSize",
      "buyer_discount": 0.15,
      "house_fee": 1
    },
    "provably_fair": {
      "hash_function": "SHA-256",
      "seed_size_bytes": 32,
      "anchor_chain": "Solana",
      "anchor_method": "memo transaction",
      "verification": "buyer can recompute SHA-256(serverSeed) and check match against on-chain memo before reveal"
    }
  },
  "theme": {
    "presets": [
      {"key": "minimal", "vibe": "Black + white. Calm.", "dramatic": false},
      {"key": "standard", "vibe": "Everything on, balanced.", "dramatic": false},
      {"key": "cinematic", "vibe": "Display type. Big.", "dramatic": false},
      {"key": "classic", "vibe": "Serif elegance.", "dramatic": false},
      {"key": "terminal", "vibe": "Mono. Trader vibe.", "dramatic": false},
      {"key": "auction", "vibe": "Sotheby's premium.", "dramatic": false},
      {"key": "vault", "vibe": "Gold + black. Premium tier.", "dramatic": true, "signature_palette": ["#d4a574", "#1a1a1a"]},
      {"key": "neon", "vibe": "Cyber. Trading floor.", "dramatic": true, "signature_palette": ["#a3e635", "#ec4899"]},
      {"key": "editorial", "vibe": "Magazine. Storyteller.", "dramatic": true, "signature_palette": ["#dc2626", "#fef3c7"]}
    ],
    "fields_required_when_adding": [
      "prisma/schema.prisma — Storefront model",
      "src/lib/storefront-theme.ts — interface + BASE_DEFAULTS + resolver + each preset that overrides",
      "src/app/api/storefronts/[slug]/theme/route.ts — validator",
      "src/app/dashboard/[slug]/theme/ThemeStudio.tsx — initial + state + form control",
      "src/app/[slug]/page.tsx — render conditionally if visual"
    ]
  },
  "documentation": {
    "deployed_at": "slabtrack.io/docs/",
    "source_path": "slabtrack/frontend/public/docs/",
    "manuals": ["operator", "seller", "buyer", "collector", "ai"],
    "asset_files": ["assets/style.css", "assets/manual.js", "assets/sidebar.js"],
    "sidebar_config": "assets/sidebar.js — single NAV constant"
  },
  "deployment": {
    "platform": "Railway",
    "services_per_repo": 2,
    "service_types": ["app service", "Postgres database"],
    "start_script_pattern": "prisma db push --accept-data-loss --skip-generate || true; next start"
  },
  "common_pitfalls": [
    {
      "symptom": "Save succeeds but public storefront unchanged",
      "cause": "Cinematic preset doesn't set logoUrl. Only 'Apply SlabTrack Showcase' does.",
      "fix": "Use APPLY SLABTRACK SHOWCASE button + Save"
    },
    {
      "symptom": "Webhook 401 signature mismatch",
      "cause": "STRIPE_WEBHOOK_SECRET doesn't match endpoint's signing secret. Test/live have different secrets.",
      "fix": "Stripe dashboard → Developers → Webhooks → reveal signing secret → update Railway env"
    },
    {
      "symptom": "Order stays pending forever",
      "cause": "Webhook didn't fire OR processing exception",
      "fix": "Check Stripe dashboard webhook log + Railway logs at the timestamp"
    },
    {
      "symptom": "Card stuck on ToT after manual delist",
      "cause": "Drift between SlabTrack card_channels and ToT actual",
      "fix": "Sync Center → Reconcile Now (or click 'Resync from ToT' in Lot Builder)"
    },
    {
      "symptom": "Strategist commit silently dropped lots",
      "cause": "MAX_CARDS_PER_LOT=25 cap. Lots bigger than 25 are skipped.",
      "fix": "Check response.skipped[] for reasons. Re-bundle smaller."
    }
  ]
}
