diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3a5e314 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +DATABASE_URL="postgresql://user:password@localhost:5432/bitcoin_model?schema=public" +JWT_SECRET="super-secret-jwt" +JWT_EXPIRY_SECONDS=86400 +JWT_COOKIE_NAME="access_token" +COOKIE_SECRET="very-secret-cookie" +BTC_PRICE_API="https://api.coindesk.com/v1/bpi/currentprice/BTC.json" +CORS_ORIGIN="http://localhost:4200" +PORT=3000 +HOST=0.0.0.0 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..f78a66d --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ['next/core-web-vitals'], +}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3b25e95 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,138 @@ +name: Quality Gates + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + actions: read + checks: write + statuses: write + +env: + PNPM_VERSION: 8.15.7 + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v2 + with: + version: ${{ env.PNPM_VERSION }} + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install dependencies + run: pnpm install --frozen-lockfile=false + - run: pnpm lint + + typecheck: + name: TypeScript + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v2 + with: + version: ${{ env.PNPM_VERSION }} + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install dependencies + run: pnpm install --frozen-lockfile=false + - run: pnpm typecheck + + unit-tests: + name: Unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v2 + with: + version: ${{ env.PNPM_VERSION }} + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install dependencies + run: pnpm install --frozen-lockfile=false + - run: pnpm test + + playwright: + name: Playwright smoke tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v2 + with: + version: ${{ env.PNPM_VERSION }} + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install dependencies + run: pnpm install --frozen-lockfile=false + - name: Install Playwright browsers + run: pnpm exec playwright install --with-deps chromium + - run: pnpm test:e2e + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report + if-no-files-found: ignore + + lighthouse: + name: Lighthouse CI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v2 + with: + version: ${{ env.PNPM_VERSION }} + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install dependencies + run: pnpm install --frozen-lockfile=false + - name: Run Lighthouse CI + run: pnpm audit:lighthouse + - uses: actions/upload-artifact@v4 + if: always() + with: + name: lighthouse-report + path: reports/lighthouse + if-no-files-found: ignore + + axe: + name: Axe accessibility audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v2 + with: + version: ${{ env.PNPM_VERSION }} + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install dependencies + run: pnpm install --frozen-lockfile=false + - name: Run Axe CLI + run: pnpm audit:axe + + metrics: + name: Metric thresholds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v2 + with: + version: ${{ env.PNPM_VERSION }} + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install dependencies + run: pnpm install --frozen-lockfile=false + - run: pnpm metrics:check diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5630d81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +node_modules +.pnpm-store +.DS_Store +coverage +playwright-report +reports/lighthouse +.next +out +.env.local +.env +coverage +.DS_Store +node_modules/ +dist/ +.env +coverage/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* +.DS_Store +.idea +.tmp diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..5f42c40 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +#!/bin/sh +pnpm exec lint-staged diff --git a/README.md b/README.md index ad82682..afbc362 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,12 @@ # Bitcoin24 -Helping you drive Bitcoin adoption. +A web app of the opensource Bitcoin24 Model inspired by: +- Michael J. Saylor +- Shirish Jajodia +- Chaitanya Jain (CJ) ## 21-year macro forecast with micro models for bitcoin strategies - - - - - - - - -
NormieBTC 10%BTC MaxiDouble MaxiTriple Maxi
-Bitcoin24 is designed to simulate 21-year outcomes of various Bitcoin strategies tailored for individuals, corporations, institutions, and nation-states. Users can input their own assumptions or adjust the model to explore different scenarios. Saving the file will automatically update the scenario comparison charts in the micro models' bottom section. +This web app is a 21-year macro forecast based on the Bitcoin24 open source model designed to simulate 21-year outcomes of various Bitcoin strategies tailored for individuals, corporations, institutions, and nation-states. Users can input their own assumptions or adjust the model to explore different scenarios. Bitcoin24 does not model Bitcoin's volatility, as its volatility profile has evolved and will continue to do so in the future. This is a simplified model intended to show possible long-term outcomes of adopting a Bitcoin standard. @@ -22,11 +16,6 @@ Bitcoin24 does not model Bitcoin's volatility, as its volatility profile has evo
-### Original Contributors -- Michael J. Saylor -- Shirish Jajodia -- Chaitanya Jain (CJ) -

@@ -35,26 +24,78 @@ Bitcoin24 does not model Bitcoin's volatility, as its volatility profile has evo

-> [!TIP] -> 1. Bitcoin24 - Intro -> 2. Bitcoin24 - BTC -> 3. Bitcoin24 - Macro -> 4. Bitcoin24 - Individual -> 5. Bitcoin24 - Corporate -> 6. Bitcoin24 - Institution -> 7. Bitcoin24 - Nation State -> 8. Bitcoin24 - United States +### Model Types +> 1. Bitcoin24 - BTC +> 2. Bitcoin24 - Macro +> 3. Bitcoin24 - Individual +> 4. Bitcoin24 - Corporate +> 5. Bitcoin24 - Institution +> 6. Bitcoin24 - Nation State +> 7. Bitcoin24 - United States
+ +### Strategy Options + + + + + + + + +
NormieBTC 10%BTC MaxiDouble MaxiTriple Maxi
+

+# Development Documentation + - [docs/development_plan.md](docs/development_plan.md) – master roadmap for every initiative. + - [docs/design_system.md](docs/design_system.md) – design system and core tech stack (tasks 1 & 21). + - [docs/theming_motion_framework.md](docs/theming_motion_framework.md) – shared theming and motion implementation (tasks 2 & 21). + - [docs/flow_specific_ux_interactions.md](docs/flow_specific_ux_interactions.md) – onboarding, navigation, and validation behaviors (tasks 3, 10, 14, 22). + - [docs/performance_accessibility_standards.md](docs/performance_accessibility_standards.md) – performance, accessibility, and observability guardrails (task 4). + - `.github/workflows/ci.yml`, `lighthouserc.json`, and `config/metrics/slo.json` – automated quality gates, Lighthouse budgets, and telemetry thresholds backing the standards. + - [docs/shared_app_foundation.md](docs/shared_app_foundation.md) – architectural blueprint for services, data ingestion, and tooling (task 5). + - [docs/authentication_account_persistence.md](docs/authentication_account_persistence.md) – authentication and scenario persistence (tasks 6 & 11). + - [docs/scenario_persistence_controls.md](docs/scenario_persistence_controls.md) – scenario save/load UX, APIs, and revision handling (task 11). + - [docs/dynamic_base_year_handling.md](docs/dynamic_base_year_handling.md) – dynamic base-year calculations and historical pricing backbone (task 12). + - [docs/onboarding_wizard.md](docs/onboarding_wizard.md) – Get Started flow implementation (tasks 7 & 14). + - [docs/user_home_page.md](docs/user_home_page.md) – authenticated dashboard experience (task 8). + - [docs/route_guarding_navigation.md](docs/route_guarding_navigation.md) – protected routing and global navigation (task 9). + - [docs/guided_model_flow.md](docs/guided_model_flow.md) – guided BTC → Macro → model experience (task 10). + + + + +>Disclaimer: The information provided here is for general informational purposes only and should not be considered financial advice. It contains forward-looking information that is inherently unknowable. You should seek advice from a professional financial advisor and other trusted sources before acting on any of this information. The authors and publishers of this information disclaim responsibility for any action taken by users of this information. This is but one view of potential outcomes. You should inform yourself of other views, including those that might disagree. ->Additional Information: The information provided here is for general informational purposes only and should not be considered financial advice. It contains forward-looking information that is inherently unknowable. You should seek advice from a professional financial advisor and other trusted sources before acting on any of this information. The authors and publishers of this information disclaim responsibility for any action taken by users of this information. This is but one view of potential outcomes. You should inform yourself of other views, including those that might disagree. +## Backend Services +This repository now includes a NestJS/Fastify backend that exposes REST and GraphQL APIs backed by PostgreSQL via Prisma. Key features include: +- JWT authentication with httpOnly cookies for REST and GraphQL requests. +- Scenario CRUD APIs and GraphQL resolvers persisted to PostgreSQL (`scenarios` table). +- BTC price ingestion stored in the `btc_prices` table, including an automated cron sync. +- Model execution endpoint that combines scenario inputs with the latest BTC price for quick analytics. +### Getting Started +1. Copy `.env.example` to `.env` and update `DATABASE_URL`, `JWT_SECRET`, and any other environment values. +2. Install dependencies and generate the Prisma client: + ```bash + npm install + npm run prisma:generate + ``` +3. Apply database migrations: + ```bash + npm run prisma:migrate + ``` +4. Start the development server: + ```bash + npm run start:dev + ``` +The REST API is served on `http://localhost:3000` and the GraphQL playground is available at `http://localhost:3000/graphql`. diff --git a/app/(auth)/home/page.tsx b/app/(auth)/home/page.tsx new file mode 100644 index 0000000..768808b --- /dev/null +++ b/app/(auth)/home/page.tsx @@ -0,0 +1,17 @@ +import { HomeHero } from '@/src/components/home/HomeHero'; +import { GuidedFlowBanner } from '@/src/components/home/GuidedFlowBanner'; +import { LivePriceBanner } from '@/src/components/home/LivePriceBanner'; +import { ModelCatalog } from '@/src/components/home/ModelCatalog'; +import { ScenarioLibrary } from '@/src/components/home/ScenarioLibrary'; + +export default function HomePage() { + return ( +
+ + + + + +
+ ); +} diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx new file mode 100644 index 0000000..181a453 --- /dev/null +++ b/app/(auth)/layout.tsx @@ -0,0 +1,5 @@ +import { AuthenticatedShell } from '@/src/components/layout/AuthenticatedShell'; + +export default function AuthLayout({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/app/(auth)/models/btc/page.tsx b/app/(auth)/models/btc/page.tsx new file mode 100644 index 0000000..ea92216 --- /dev/null +++ b/app/(auth)/models/btc/page.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { useRouter } from 'next/navigation'; +import { FlowShell } from '@/src/components/flow/FlowShell'; +import { BTCInputsPanel } from '@/src/components/models/BTCInputsPanel'; +import { BTCResults } from '@/src/components/models/BTCResults'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; + +export default function BTCModelPage() { + const markStep = useGuidedFlowStore((state) => state.markStep); + const router = useRouter(); + + return ( + { + markStep('btc', { status: 'draft' }); + router.push('/home'); + }} + onNext={() => { + markStep('btc', { status: 'complete' }); + router.push('/models/macro'); + }} + nextLabel="Next: Macro" + > + + + + ); +} diff --git a/app/(auth)/models/corporate/page.tsx b/app/(auth)/models/corporate/page.tsx new file mode 100644 index 0000000..e2ce28f --- /dev/null +++ b/app/(auth)/models/corporate/page.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { useRouter } from 'next/navigation'; +import { FlowShell } from '@/src/components/flow/FlowShell'; +import { ModelDetail } from '@/src/components/models/ModelDetail'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; + +export default function CorporateModelPage() { + const markStep = useGuidedFlowStore((state) => state.markStep); + const router = useRouter(); + + return ( + { + markStep('model', { status: 'draft' }); + router.push('/models/macro'); + }} + onNext={() => { + markStep('model', { status: 'complete' }); + router.push('/home'); + }} + backLabel="Back: Macro" + nextLabel="Finish & Save" + > + + + ); +} diff --git a/app/(auth)/models/individual/page.tsx b/app/(auth)/models/individual/page.tsx new file mode 100644 index 0000000..d1eb9fb --- /dev/null +++ b/app/(auth)/models/individual/page.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { useRouter } from 'next/navigation'; +import { FlowShell } from '@/src/components/flow/FlowShell'; +import { ModelDetail } from '@/src/components/models/ModelDetail'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; + +export default function IndividualModelPage() { + const markStep = useGuidedFlowStore((state) => state.markStep); + const router = useRouter(); + + return ( + { + markStep('model', { status: 'draft' }); + router.push('/models/macro'); + }} + onNext={() => { + markStep('model', { status: 'complete' }); + router.push('/home'); + }} + backLabel="Back: Macro" + nextLabel="Finish & Save" + > + + + ); +} diff --git a/app/(auth)/models/institution/page.tsx b/app/(auth)/models/institution/page.tsx new file mode 100644 index 0000000..53deef1 --- /dev/null +++ b/app/(auth)/models/institution/page.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { useRouter } from 'next/navigation'; +import { FlowShell } from '@/src/components/flow/FlowShell'; +import { ModelDetail } from '@/src/components/models/ModelDetail'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; + +export default function InstitutionModelPage() { + const markStep = useGuidedFlowStore((state) => state.markStep); + const router = useRouter(); + + return ( + { + markStep('model', { status: 'draft' }); + router.push('/models/macro'); + }} + onNext={() => { + markStep('model', { status: 'complete' }); + router.push('/home'); + }} + backLabel="Back: Macro" + nextLabel="Finish & Save" + > + + + ); +} diff --git a/app/(auth)/models/macro/page.tsx b/app/(auth)/models/macro/page.tsx new file mode 100644 index 0000000..5d20afe --- /dev/null +++ b/app/(auth)/models/macro/page.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useRouter } from 'next/navigation'; +import { FlowShell } from '@/src/components/flow/FlowShell'; +import { MacroPanel } from '@/src/components/models/MacroPanel'; +import { MacroResults } from '@/src/components/models/MacroResults'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; + +export default function MacroModelPage() { + const markStep = useGuidedFlowStore((state) => state.markStep); + const router = useRouter(); + + return ( + { + markStep('macro', { status: 'draft' }); + router.push('/models/btc'); + }} + onNext={() => { + markStep('macro', { status: 'complete' }); + router.push('/models/individual'); + }} + backLabel="Back: BTC" + nextLabel="Next: Model" + > + + + + ); +} diff --git a/app/(auth)/models/nation/page.tsx b/app/(auth)/models/nation/page.tsx new file mode 100644 index 0000000..a7a6300 --- /dev/null +++ b/app/(auth)/models/nation/page.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { useRouter } from 'next/navigation'; +import { FlowShell } from '@/src/components/flow/FlowShell'; +import { ModelDetail } from '@/src/components/models/ModelDetail'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; + +export default function NationModelPage() { + const markStep = useGuidedFlowStore((state) => state.markStep); + const router = useRouter(); + + return ( + { + markStep('model', { status: 'draft' }); + router.push('/models/macro'); + }} + onNext={() => { + markStep('model', { status: 'complete' }); + router.push('/home'); + }} + backLabel="Back: Macro" + nextLabel="Finish & Save" + > + + + ); +} diff --git a/app/(auth)/models/page.tsx b/app/(auth)/models/page.tsx new file mode 100644 index 0000000..95e0109 --- /dev/null +++ b/app/(auth)/models/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from 'next/navigation'; + +export default function ModelsIndex() { + redirect('/models/btc'); +} diff --git a/app/(public)/onboarding/page.tsx b/app/(public)/onboarding/page.tsx new file mode 100644 index 0000000..b84d228 --- /dev/null +++ b/app/(public)/onboarding/page.tsx @@ -0,0 +1,101 @@ +'use client'; + +import { useEffect, useMemo } from 'react'; +import { useRouter } from 'next/navigation'; +import { WizardLayout } from '@/src/components/onboarding/WizardLayout'; +import { WelcomeStep } from '@/src/components/onboarding/WelcomeStep'; +import { AuthStep } from '@/src/components/onboarding/AuthStep'; +import { PriceStep } from '@/src/components/onboarding/PriceStep'; +import { useOnboardingStore } from '@/src/state/onboardingStore'; +import { useAuthStore } from '@/src/state/authStore'; +import { useScenarioStore } from '@/src/state/scenarioStore'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; + +const TOTAL_STEPS = 3; + +export default function OnboardingPage() { + const router = useRouter(); + const auth = useAuthStore(); + const onboarding = useOnboardingStore(); + const scenarioStore = useScenarioStore(); + const guidedFlow = useGuidedFlowStore(); + + useEffect(() => { + if (auth.user && onboarding.step === 0) { + onboarding.setStep(1); + onboarding.setMode('login'); + } + }, [auth.user, onboarding]); + + useEffect(() => { + if (auth.user && auth.onboardingComplete) { + router.replace('/home'); + } + }, [auth.user, auth.onboardingComplete, router]); + + const step = onboarding.step; + const title = useMemo(() => { + switch (step) { + case 0: + return 'Let\u2019s set the stage'; + case 1: + return onboarding.mode === 'signup' ? 'Create your account' : 'Welcome back'; + case 2: + return 'Lock in your starting price'; + default: + return 'Onboarding'; + } + }, [step, onboarding.mode]); + + const description = useMemo(() => { + switch (step) { + case 0: + return 'Three guided steps connect you to the modeling platform. Progress auto-saves in case you need to pause.'; + case 1: + return onboarding.mode === 'signup' + ? 'Securely create your account with inline validation and password guidance.' + : 'Sign back in to resume your modeling scenarios with everything preserved.'; + case 2: + return 'Choose a price anchor that will inform BTC projections across every screen.'; + default: + return ''; + } + }, [step, onboarding.mode]); + + function handlePriceConfirm(selection: { mode: 'live' | 'historical' | 'custom'; customPrice?: number; historicalDate?: string }) { + onboarding.setPriceSelection(selection); + onboarding.complete(); + auth.markOnboardingComplete(); + const scenario = scenarioStore.createScenario('My Bitcoin Thesis', 'individual'); + if (selection.customPrice) { + scenarioStore.updateBTCInputs(scenario.id, (draft) => { + draft.currentPrice = selection.customPrice ?? draft.currentPrice; + }); + } + guidedFlow.setScenario(scenario.id); + guidedFlow.markStep('btc', { status: 'in-progress' }); + router.push('/home?onboarding=complete'); + } + + return ( + + {step === 0 && ( + onboarding.setStep(1)} + onLogin={() => { + onboarding.setMode('login'); + onboarding.setStep(1); + }} + /> + )} + {step === 1 && ( + onboarding.setMode(mode)} + onSuccess={() => onboarding.setStep(2)} + /> + )} + {step === 2 && } + + ); +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..6fcdb99 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,30 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + color-scheme: dark; +} + +body { + @apply bg-bg-base text-text-primary font-sans; + background-image: radial-gradient(circle at top left, rgba(247, 147, 26, 0.1), transparent 55%), + radial-gradient(circle at bottom right, rgba(45, 212, 191, 0.08), transparent 50%); + min-height: 100vh; +} + +main { + min-height: 100vh; +} + +.glass-card { + @apply bg-bg-surface backdrop-blur-xl border border-border-subtle rounded-3xl shadow-glass; +} + +.section-heading { + @apply text-2xl md:text-3xl font-display font-semibold tracking-tight; +} + +.text-subtle { + @apply text-text-secondary; +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..fcce6c9 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,25 @@ +import './globals.css'; +import type { Metadata } from 'next'; +import { Inter, Space_Grotesk } from 'next/font/google'; +import { AppProviders } from '@/src/providers/AppProviders'; + +const inter = Inter({ subsets: ['latin'], variable: '--font-inter' }); +const spaceGrotesk = Space_Grotesk({ subsets: ['latin'], variable: '--font-space-grotesk' }); + +export const metadata: Metadata = { + title: 'Bitcoin24 Modeling Platform', + description: 'Model 21-year Bitcoin adoption scenarios with guided workflows.', + icons: { + icon: '/favicon.ico' + } +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..0a2d1b2 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,44 @@ +import Link from 'next/link'; +import { ArrowRight, ShieldCheck, Sparkles } from 'lucide-react'; + +export default function CoverPage() { + return ( +
+
+

Bitcoin24 Modeling Platform

+

+ Model Bitcoin adoption scenarios with cinematic clarity. +

+

+ Guided onboarding, live pricing, and persistent scenarios help you explore Bitcoin's 21-year trajectory for + individuals, corporations, institutions, and nation states—all inspired by the Bitcoin24 open workbook. +

+
+ + Get started + + + + I already have an account + +
+
+
+ + Secure scenario persistence with local-first resilience and optimistic auto-save. +
+
+ + Guided BTC → Macro → Model flows keep your assumptions aligned across every screen. +
+
+
+
+ ); +} diff --git a/apps/api/.eslintrc.cjs b/apps/api/.eslintrc.cjs new file mode 100644 index 0000000..eaa8aea --- /dev/null +++ b/apps/api/.eslintrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + extends: ['@bitcoin24/config/eslint'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'] + } +}; diff --git a/apps/api/jest.config.cjs b/apps/api/jest.config.cjs new file mode 100644 index 0000000..6a1d367 --- /dev/null +++ b/apps/api/jest.config.cjs @@ -0,0 +1,15 @@ +/** @type {import('jest').Config} */ +module.exports = { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + extensionsToTreatAsEsm: ['.ts'], + roots: ['/src'], + globals: { + 'ts-jest': { + useESM: true + } + }, + moduleNameMapper: { + '^@bitcoin24/models(.*)$': '/../../packages/models/src$1' + } +}; diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000..66fe3a7 --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,38 @@ +{ + "name": "@bitcoin24/api", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/main.ts", + "build": "tsc -p tsconfig.build.json", + "start": "node dist/main.js", + "lint": "eslint \"src/**/*.ts\"", + "test": "jest", + "format": "prettier --write \"src/**/*.ts\"" + }, + "dependencies": { + "@fastify/cors": "^10.0.1", + "@fastify/sensible": "^5.0.1", + "fastify": "^4.26.2", + "zod": "^3.23.8" + }, + "devDependencies": { + "@bitcoin24/config": "workspace:*", + "@types/jest": "^29.5.12", + "@types/node": "^20.12.7", + "@typescript-eslint/eslint-plugin": "^7.7.1", + "@typescript-eslint/parser": "^7.7.1", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jsx-a11y": "^6.8.0", + "eslint-plugin-react": "^7.34.2", + "eslint-plugin-react-hooks": "^4.6.0", + "jest": "^29.7.0", + "prettier": "^3.2.5", + "prettier-plugin-tailwindcss": "^0.5.11", + "ts-jest": "^29.1.2", + "tsx": "^4.7.1", + "typescript": "^5.4.5" + } +} diff --git a/apps/api/prettier.config.cjs b/apps/api/prettier.config.cjs new file mode 100644 index 0000000..1dedfae --- /dev/null +++ b/apps/api/prettier.config.cjs @@ -0,0 +1 @@ +module.exports = require('@bitcoin24/config/prettier'); diff --git a/apps/api/src/__tests__/scenario.test.ts b/apps/api/src/__tests__/scenario.test.ts new file mode 100644 index 0000000..de3975a --- /dev/null +++ b/apps/api/src/__tests__/scenario.test.ts @@ -0,0 +1,12 @@ +import { buildServer } from '../main'; + +describe('scenario routes', () => { + it('returns all scenarios', async () => { + const server = await buildServer(); + const response = await server.inject({ method: 'GET', url: '/scenarios' }); + + expect(response.statusCode).toBe(200); + const payload = response.json(); + expect(payload.scenarios.length).toBeGreaterThan(0); + }); +}); diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts new file mode 100644 index 0000000..64c7e7d --- /dev/null +++ b/apps/api/src/main.ts @@ -0,0 +1,30 @@ +import Fastify from 'fastify'; +import sensible from '@fastify/sensible'; +import cors from '@fastify/cors'; +import { fileURLToPath } from 'url'; +import { scenarioRoutes } from './routes/scenario'; + +export async function buildServer() { + const app = Fastify({ + logger: true + }); + + await app.register(cors, { origin: '*' }); + await app.register(sensible); + await app.register(scenarioRoutes, { prefix: '/scenarios' }); + + app.get('/health', async () => ({ status: 'ok' })); + + return app; +} + +const isDirectRun = fileURLToPath(import.meta.url) === process.argv[1]; + +if (isDirectRun) { + buildServer() + .then((server) => server.listen({ port: Number(process.env.PORT) || 3001, host: '0.0.0.0' })) + .catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/apps/api/src/routes/scenario.ts b/apps/api/src/routes/scenario.ts new file mode 100644 index 0000000..fb106c3 --- /dev/null +++ b/apps/api/src/routes/scenario.ts @@ -0,0 +1,24 @@ +import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; +import { defaultGrowthScenarios } from '@bitcoin24/models'; +import { z } from 'zod'; + +const scenarioParamsSchema = z.object({ + id: z.string() +}); + +type ScenarioParams = z.infer; + +export async function scenarioRoutes(app: FastifyInstance, _opts: FastifyPluginOptions) { + app.get('/', async () => ({ scenarios: defaultGrowthScenarios })); + + app.get<{ Params: ScenarioParams }>('/:id', async (request, reply) => { + const { id } = scenarioParamsSchema.parse(request.params); + const scenario = defaultGrowthScenarios.find((item) => item.id === id); + + if (!scenario) { + return reply.notFound('Scenario not found'); + } + + return { scenario }; + }); +} diff --git a/apps/api/tsconfig.build.json b/apps/api/tsconfig.build.json new file mode 100644 index 0000000..6dcf057 --- /dev/null +++ b/apps/api/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false + }, + "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 0000000..5bf58d3 --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@bitcoin24/config/tsconfig/base.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/*.ts"] +} diff --git a/apps/web/.eslintrc.cjs b/apps/web/.eslintrc.cjs new file mode 100644 index 0000000..5ccc014 --- /dev/null +++ b/apps/web/.eslintrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + extends: ['next/core-web-vitals', '@bitcoin24/config/eslint'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'] + } +}; diff --git a/apps/web/.eslintrc.json b/apps/web/.eslintrc.json new file mode 100644 index 0000000..16a5de1 --- /dev/null +++ b/apps/web/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "extends": ["next/core-web-vitals"], + "rules": { + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn" + } +} diff --git a/apps/web/app/auth/layout.tsx b/apps/web/app/auth/layout.tsx new file mode 100644 index 0000000..983b9ee --- /dev/null +++ b/apps/web/app/auth/layout.tsx @@ -0,0 +1,11 @@ +import { ReactNode } from 'react'; + +export default function AuthLayout({ children }: { children: ReactNode }) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/apps/web/app/auth/login/page.tsx b/apps/web/app/auth/login/page.tsx new file mode 100644 index 0000000..b9a0e6d --- /dev/null +++ b/apps/web/app/auth/login/page.tsx @@ -0,0 +1,83 @@ +'use client'; + +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { FormEvent, useState } from 'react'; +import { useAuthStore } from '../../../src/stores/authStore'; + +export default function LoginPage() { + const router = useRouter(); + const { login, setLoading, setError, status, error } = useAuthStore((state) => ({ + login: state.login, + setLoading: state.setLoading, + setError: state.setError, + status: state.status, + error: state.error + })); + const [email, setEmail] = useState('satoshi@bitcoin.org'); + const [password, setPassword] = useState('bitcoin24'); + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + setLoading(); + + if (!email || !password) { + setError('Please provide email and password.'); + return; + } + + setTimeout(() => { + login({ + user: { + id: 'user-1', + email, + name: 'Bitcoin Strategist', + roles: ['admin'] + }, + token: 'mock-jwt-token' + }); + router.replace('/protected'); + }, 300); + }; + + return ( +
+
+

Welcome back

+

+ Sign in to continue exploring Bitcoin24 scenarios. +

+
+
+ + +
+ {status === 'error' &&

{error}

} + +

+ Need an account? Sign up +

+
+ ); +} diff --git a/apps/web/app/auth/signup/page.tsx b/apps/web/app/auth/signup/page.tsx new file mode 100644 index 0000000..6f1638f --- /dev/null +++ b/apps/web/app/auth/signup/page.tsx @@ -0,0 +1,22 @@ +'use client'; + +import Link from 'next/link'; + +export default function SignupPage() { + return ( +
+
+

Request Access

+

+ Signup is invite-only while we finalize pricing tiers. +

+
+

+ Reach out to hello@bitcoin24.app to request an early access code. +

+ + Back to login + +
+ ); +} diff --git a/apps/web/app/global.css b/apps/web/app/global.css new file mode 100644 index 0000000..a7a59f9 --- /dev/null +++ b/apps/web/app/global.css @@ -0,0 +1,21 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--font-body), system-ui, sans-serif; + background-color: var(--color-bg-base); + color: var(--color-text-primary); +} + +a { + color: var(--color-accent); +} + +[data-theme='dark'] a { + color: var(--color-accent); +} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx new file mode 100644 index 0000000..4f9fc1f --- /dev/null +++ b/apps/web/app/layout.tsx @@ -0,0 +1,22 @@ +import './global.css'; +import type { Metadata } from 'next'; +import { ReactNode } from 'react'; +import { ThemeProvider } from '@bitcoin24/ui'; +import { Providers } from '../src/providers/Providers'; + +export const metadata: Metadata = { + title: 'Bitcoin24 Model', + description: 'Scenario planning workspace for Bitcoin adoption strategies.' +}; + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + + + {children} + + + + ); +} diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx new file mode 100644 index 0000000..d15fce9 --- /dev/null +++ b/apps/web/app/page.tsx @@ -0,0 +1,27 @@ +import Link from 'next/link'; +import { redirect } from 'next/navigation'; +import { getSession } from '../src/lib/session'; + +export default async function LandingPage() { + const session = await getSession(); + if (session?.user) { + redirect('/protected'); + } + + return ( +
+

Bitcoin24 Model

+

+ Explore macro and micro bitcoin strategies through immersive data visualizations and guided scenarios. +

+
+ + Enter Workspace + + + View Documentation + +
+
+ ); +} diff --git a/apps/web/app/protected/layout.tsx b/apps/web/app/protected/layout.tsx new file mode 100644 index 0000000..a635b3f --- /dev/null +++ b/apps/web/app/protected/layout.tsx @@ -0,0 +1,16 @@ +import { ReactNode } from 'react'; +import { AppShell } from '@bitcoin24/ui'; +import { AuthGuard } from '../../src/components/AuthGuard'; +import { ScenarioIndicator } from '../../src/components/ScenarioIndicator'; +import { PricingTicker } from '../../src/components/PricingTicker'; +import { PageTransition } from '../../src/components/PageTransition'; + +export default function ProtectedLayout({ children }: { children: ReactNode }) { + return ( + + } sidebar={}> + {children} + + + ); +} diff --git a/apps/web/app/protected/page.tsx b/apps/web/app/protected/page.tsx new file mode 100644 index 0000000..6a87757 --- /dev/null +++ b/apps/web/app/protected/page.tsx @@ -0,0 +1,51 @@ +'use client'; + +import { Card } from '@bitcoin24/ui'; +import { useAuthStore, selectUserProfile } from '../../src/stores/authStore'; +import { useScenarioStore } from '../../src/stores/scenarioStore'; +import { useQuery } from '@tanstack/react-query'; +import { fetchLatestPrice } from '../../src/lib/pricing'; +import { usePricingStore } from '../../src/stores/pricingStore'; + +export default function ProtectedHome() { + const user = useAuthStore(selectUserProfile); + const { upsertScenario } = useScenarioStore(); + const setLatest = usePricingStore((state) => state.setLatest); + + useQuery({ + queryKey: ['pricing', 'latest', 'prefetch'], + queryFn: fetchLatestPrice, + refetchInterval: 60_000, + onSuccess: (price) => setLatest(price) + }); + + return ( +
+ +
+

Use the navigation rail to switch between macro, micro, and nation-state models.

+ +
+
+ +
    +
  1. 1. Review macro assumptions and adjust base year inputs.
  2. +
  3. 2. Configure your preferred strategy (individual, corporate, etc.).
  4. +
  5. 3. Compare outcomes and export summaries.
  6. +
+
+
+ ); +} diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/apps/web/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/web/next.config.js b/apps/web/next.config.js new file mode 100644 index 0000000..558fa4a --- /dev/null +++ b/apps/web/next.config.js @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + typedRoutes: true + } +}; + +module.exports = nextConfig; diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs new file mode 100644 index 0000000..5b4c6ca --- /dev/null +++ b/apps/web/next.config.mjs @@ -0,0 +1,9 @@ +import type { NextConfig } from 'next'; + +const config: NextConfig = { + experimental: { + serverActions: true + } +}; + +export default config; diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..2894c02 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,35 @@ +{ + "name": "web", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@bitcoin24/ui": "workspace:*", + "@tanstack/react-query": "^5.28.4", + "@tanstack/react-query-devtools": "^5.28.4", + "clsx": "^2.1.0", + "framer-motion": "^11.0.0", + "next": "^14.1.0", + "next-themes": "^0.2.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "zustand": "^4.5.2" + }, + "devDependencies": { + "@types/node": "^20.10.6", + "@types/react": "^18.2.21", + "@types/react-dom": "^18.2.7", + "autoprefixer": "^10.4.16", + "eslint": "^8.56.0", + "eslint-config-next": "^14.1.0", + "postcss": "^8.4.32", + "tailwindcss": "^3.4.1", + "tailwindcss-animate": "^1.0.7", + "typescript": "^5.3.3" + } +} diff --git a/apps/web/postcss.config.cjs b/apps/web/postcss.config.cjs new file mode 100644 index 0000000..5cbc2c7 --- /dev/null +++ b/apps/web/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +}; diff --git a/apps/web/prettier.config.cjs b/apps/web/prettier.config.cjs new file mode 100644 index 0000000..1dedfae --- /dev/null +++ b/apps/web/prettier.config.cjs @@ -0,0 +1 @@ +module.exports = require('@bitcoin24/config/prettier'); diff --git a/apps/web/src/components/AuthGuard.tsx b/apps/web/src/components/AuthGuard.tsx new file mode 100644 index 0000000..8a706ea --- /dev/null +++ b/apps/web/src/components/AuthGuard.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { PropsWithChildren, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { useAuthStore, selectIsAuthenticated, selectAuthToken } from '../stores/authStore'; + +export const AuthGuard = ({ children }: PropsWithChildren) => { + const router = useRouter(); + const isAuthenticated = useAuthStore(selectIsAuthenticated); + const token = useAuthStore(selectAuthToken); + + useEffect(() => { + if (!isAuthenticated || !token) { + router.replace('/auth/login'); + } + }, [isAuthenticated, token, router]); + + if (!isAuthenticated || !token) { + return null; + } + + return <>{children}; +}; diff --git a/apps/web/src/components/PageTransition.tsx b/apps/web/src/components/PageTransition.tsx new file mode 100644 index 0000000..89c01e0 --- /dev/null +++ b/apps/web/src/components/PageTransition.tsx @@ -0,0 +1,13 @@ +'use client'; + +import { PropsWithChildren } from 'react'; +import { AnimatePresence, motion } from 'framer-motion'; +import { routeTransition } from '@bitcoin24/ui'; + +export const PageTransition = ({ children }: PropsWithChildren) => ( + + + {children} + + +); diff --git a/apps/web/src/components/PricingTicker.tsx b/apps/web/src/components/PricingTicker.tsx new file mode 100644 index 0000000..b21fd1d --- /dev/null +++ b/apps/web/src/components/PricingTicker.tsx @@ -0,0 +1,37 @@ +'use client'; + +import { useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { motion } from 'framer-motion'; +import { fadeInUp } from '@bitcoin24/ui'; +import { usePricingStore, selectLatestPrice } from '../stores/pricingStore'; +import { fetchLatestPrice } from '../lib/pricing'; + +export const PricingTicker = () => { + const setLatest = usePricingStore((state) => state.setLatest); + const latest = usePricingStore(selectLatestPrice); + + const { data, isFetching } = useQuery({ + queryKey: ['pricing', 'latest'], + queryFn: fetchLatestPrice, + refetchInterval: 30_000 + }); + + useEffect(() => { + if (data) { + setLatest(data); + } + }, [data, setLatest]); + + return ( + +
+

Live Price

+

+ {latest ? `$${latest.priceUsd.toLocaleString()}` : 'Loading...'} +

+
+ {isFetching ? 'Refreshing' : latest?.source ?? 'API'} +
+ ); +}; diff --git a/apps/web/src/components/ScenarioIndicator.tsx b/apps/web/src/components/ScenarioIndicator.tsx new file mode 100644 index 0000000..976f049 --- /dev/null +++ b/apps/web/src/components/ScenarioIndicator.tsx @@ -0,0 +1,43 @@ +'use client'; + +import Link from 'next/link'; +import { useMemo } from 'react'; +import { useScenarioStore, selectActiveScenario, selectScenarioDirty } from '../stores/scenarioStore'; + +const scenarioLabel: Record = { + btc: 'BTC Model', + macro: 'Macro Model', + individual: 'Individual Strategy', + corporate: 'Corporate Strategy', + institution: 'Institution Model', + nation: 'Nation-State Model' +}; + +export const ScenarioIndicator = () => { + const active = useScenarioStore(selectActiveScenario); + const dirty = useScenarioStore(selectScenarioDirty); + + const label = useMemo(() => { + if (!active) { + return 'Select a scenario to begin'; + } + const base = scenarioLabel[active.model] ?? 'Scenario'; + return `${base}: ${active.name}`; + }, [active]); + + return ( +
+
+

Scenario Status

+

{label}

+
+
+ Actions + + Manage Scenarios + + {dirty && Unsaved changes} +
+
+ ); +}; diff --git a/apps/web/src/lib/pricing.ts b/apps/web/src/lib/pricing.ts new file mode 100644 index 0000000..7cb398c --- /dev/null +++ b/apps/web/src/lib/pricing.ts @@ -0,0 +1,11 @@ +import type { PricePoint } from '../stores/pricingStore'; + +export const fetchLatestPrice = async (): Promise => { + // Placeholder implementation simulating API integration + await new Promise((resolve) => setTimeout(resolve, 250)); + return { + timestamp: new Date().toISOString(), + priceUsd: 68000 + Math.round(Math.random() * 2000 - 1000), + source: 'Mocked CoinGecko' + }; +}; diff --git a/apps/web/src/lib/session.ts b/apps/web/src/lib/session.ts new file mode 100644 index 0000000..7c7a9de --- /dev/null +++ b/apps/web/src/lib/session.ts @@ -0,0 +1,14 @@ +export type Session = { + user: { id: string; email: string; plan: 'free' | 'pro' }; + token: string; +} | null; + +let mockSession: Session = null; + +export const setMockSession = (session: Session) => { + mockSession = session; +}; + +export const getSession = async (): Promise => { + return mockSession; +}; diff --git a/apps/web/src/pages/_app.tsx b/apps/web/src/pages/_app.tsx new file mode 100644 index 0000000..9f9e7b5 --- /dev/null +++ b/apps/web/src/pages/_app.tsx @@ -0,0 +1,6 @@ +import type { AppProps } from 'next/app'; +import '@/styles/globals.css'; + +export default function App({ Component, pageProps }: AppProps) { + return ; +} diff --git a/apps/web/src/pages/index.test.tsx b/apps/web/src/pages/index.test.tsx new file mode 100644 index 0000000..0725571 --- /dev/null +++ b/apps/web/src/pages/index.test.tsx @@ -0,0 +1,10 @@ +import { describe, expect, it } from 'vitest'; +import { render } from '@testing-library/react'; +import Home from './index'; + +describe('Home page', () => { + it('renders active scenario information', () => { + const { getByText } = render(); + expect(getByText(/Bitcoin24 Modeling Portal/)).toBeInTheDocument(); + }); +}); diff --git a/apps/web/src/pages/index.tsx b/apps/web/src/pages/index.tsx new file mode 100644 index 0000000..ebd4cf3 --- /dev/null +++ b/apps/web/src/pages/index.tsx @@ -0,0 +1,30 @@ +import Head from 'next/head'; +import { Button } from '@bitcoin24/ui'; +import { defaultGrowthScenarios } from '@bitcoin24/models'; + +export default function Home() { + const scenario = defaultGrowthScenarios[0]; + + return ( + <> + + Bitcoin24 Portal + +
+
+

Bitcoin24 Modeling Portal

+

+ Prototype workspace scaffolding the web experience described in the Shared Application + Foundation blueprint. +

+
+

Active Scenario

+

{scenario.name}

+

{scenario.description}

+ +
+
+
+ + ); +} diff --git a/apps/web/src/providers/Providers.tsx b/apps/web/src/providers/Providers.tsx new file mode 100644 index 0000000..c97ed5f --- /dev/null +++ b/apps/web/src/providers/Providers.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { ReactNode, useState } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { ZustandProvider } from './ZustandProvider'; + +export const Providers = ({ children }: { children: ReactNode }) => { + const [client] = useState(() => new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30_000, + refetchOnWindowFocus: false, + retry: 1 + } + } + })); + + return ( + + {children} + + + ); +}; diff --git a/apps/web/src/providers/ZustandProvider.tsx b/apps/web/src/providers/ZustandProvider.tsx new file mode 100644 index 0000000..1b7352a --- /dev/null +++ b/apps/web/src/providers/ZustandProvider.tsx @@ -0,0 +1,5 @@ +'use client'; + +import { ReactNode } from 'react'; + +export const ZustandProvider = ({ children }: { children: ReactNode }) => <>{children}; diff --git a/apps/web/src/stores/authStore.ts b/apps/web/src/stores/authStore.ts new file mode 100644 index 0000000..7202280 --- /dev/null +++ b/apps/web/src/stores/authStore.ts @@ -0,0 +1,39 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +export type UserProfile = { + id: string; + email: string; + name: string; + roles: string[]; +}; + +export type AuthState = { + user: UserProfile | null; + token: string | null; + status: 'idle' | 'loading' | 'authenticated' | 'error'; + error?: string; + login: (payload: { user: UserProfile; token: string }) => void; + logout: () => void; + setLoading: () => void; + setError: (message: string) => void; +}; + +export const useAuthStore = create()( + persist( + (set) => ({ + user: null, + token: null, + status: 'idle', + login: ({ user, token }) => set({ user, token, status: 'authenticated', error: undefined }), + logout: () => set({ user: null, token: null, status: 'idle' }), + setLoading: () => set({ status: 'loading', error: undefined }), + setError: (message) => set({ status: 'error', error: message }) + }), + { name: 'bitcoin24-auth' } + ) +); + +export const selectIsAuthenticated = (state: AuthState) => state.status === 'authenticated' && !!state.user; +export const selectAuthToken = (state: AuthState) => state.token; +export const selectUserProfile = (state: AuthState) => state.user; diff --git a/apps/web/src/stores/pricingStore.ts b/apps/web/src/stores/pricingStore.ts new file mode 100644 index 0000000..c171c40 --- /dev/null +++ b/apps/web/src/stores/pricingStore.ts @@ -0,0 +1,31 @@ +import { create } from 'zustand'; + +export type PricePoint = { + timestamp: string; + priceUsd: number; + source: string; +}; + +export type PricingState = { + latest: PricePoint | null; + history: PricePoint[]; + isLoading: boolean; + error?: string; + setLatest: (point: PricePoint) => void; + setHistory: (points: PricePoint[]) => void; + setLoading: (isLoading: boolean) => void; + setError: (message?: string) => void; +}; + +export const usePricingStore = create((set) => ({ + latest: null, + history: [], + isLoading: false, + setLatest: (point) => set({ latest: point }), + setHistory: (points) => set({ history: points }), + setLoading: (isLoading) => set({ isLoading }), + setError: (message) => set({ error: message }) +})); + +export const selectLatestPrice = (state: PricingState) => state.latest; +export const selectPriceHistory = (state: PricingState) => state.history; diff --git a/apps/web/src/stores/scenarioStore.ts b/apps/web/src/stores/scenarioStore.ts new file mode 100644 index 0000000..3151d2c --- /dev/null +++ b/apps/web/src/stores/scenarioStore.ts @@ -0,0 +1,43 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +export type ScenarioModel = 'btc' | 'macro' | 'individual' | 'corporate' | 'institution' | 'nation'; + +export type ScenarioState = { + activeScenarioId: string | null; + model: ScenarioModel; + lastSavedAt?: string; + dirty: boolean; + scenarios: Record; + setActiveScenario: (id: string, model: ScenarioModel) => void; + upsertScenario: (scenario: { id: string; name: string; model: ScenarioModel; updatedAt: string }) => void; + markDirty: (dirty?: boolean) => void; + markSaved: () => void; +}; + +export const useScenarioStore = create()( + persist( + (set) => ({ + activeScenarioId: null, + model: 'btc', + dirty: false, + scenarios: {}, + setActiveScenario: (id, model) => set({ activeScenarioId: id, model }), + upsertScenario: (scenario) => + set((state) => ({ + scenarios: { ...state.scenarios, [scenario.id]: scenario }, + activeScenarioId: scenario.id, + model: scenario.model, + dirty: false, + lastSavedAt: scenario.updatedAt + })), + markDirty: (dirty = true) => set({ dirty }), + markSaved: () => set({ dirty: false, lastSavedAt: new Date().toISOString() }) + }), + { name: 'bitcoin24-scenarios' } + ) +); + +export const selectActiveScenario = (state: ScenarioState) => + state.activeScenarioId ? state.scenarios[state.activeScenarioId] ?? null : null; +export const selectScenarioDirty = (state: ScenarioState) => state.dirty; diff --git a/apps/web/src/styles/globals.css b/apps/web/src/styles/globals.css new file mode 100644 index 0000000..4ee2130 --- /dev/null +++ b/apps/web/src/styles/globals.css @@ -0,0 +1,11 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + color-scheme: dark; +} + +body { + font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; +} diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js new file mode 100644 index 0000000..2a94648 --- /dev/null +++ b/apps/web/tailwind.config.js @@ -0,0 +1 @@ +module.exports = require('@bitcoin24/config/tailwind'); diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts new file mode 100644 index 0000000..8e0d9e9 --- /dev/null +++ b/apps/web/tailwind.config.ts @@ -0,0 +1,18 @@ +import type { Config } from 'tailwindcss'; +import preset from '@bitcoin24/config/tailwind-preset'; +import animate from 'tailwindcss-animate'; + +const config: Config = { + presets: [preset], + content: ['app/**/*.{ts,tsx}', 'src/**/*.{ts,tsx}', '../../packages/ui/src/**/*.{ts,tsx}'], + theme: { + extend: { + gridTemplateColumns: { + dashboard: 'repeat(auto-fit, minmax(320px, 1fr))' + } + } + }, + plugins: [animate] +}; + +export default config; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..62386a8 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "preserve", + "module": "ESNext", + "moduleResolution": "Node", + "allowJs": false, + "noEmit": true, + "plugins": [{ "name": "next" }], + "types": ["node", "react", "react-dom"] + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts new file mode 100644 index 0000000..fe8fe1c --- /dev/null +++ b/apps/web/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + }, + test: { + environment: 'jsdom', + setupFiles: ['./vitest.setup.ts'] + } +}); diff --git a/apps/web/vitest.setup.ts b/apps/web/vitest.setup.ts new file mode 100644 index 0000000..7b0828b --- /dev/null +++ b/apps/web/vitest.setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/config/metrics/slo.json b/config/metrics/slo.json new file mode 100644 index 0000000..77c5289 --- /dev/null +++ b/config/metrics/slo.json @@ -0,0 +1,17 @@ +{ + "lcp": { + "unit": "milliseconds", + "threshold": 2000, + "description": "Largest Contentful Paint must remain below 2 seconds at the 75th percentile." + }, + "accessibility": { + "unit": "lighthouse-score", + "threshold": 0.95, + "description": "Accessibility Lighthouse category score must be at least 95%." + }, + "apiLatency": { + "unit": "milliseconds", + "threshold": 400, + "description": "Scenario API p95 latency limit in production." + } +} diff --git a/config/telemetry/analytics.json b/config/telemetry/analytics.json new file mode 100644 index 0000000..c97fe10 --- /dev/null +++ b/config/telemetry/analytics.json @@ -0,0 +1,29 @@ +{ + "frontend": { + "rumProvider": "vercel-analytics", + "errorTracking": "sentry", + "webVitals": { + "metrics": ["LCP", "FID", "CLS", "INP"], + "destination": "vercel-analytics" + }, + "sampleRate": { + "webVitals": 1, + "sessionReplay": 0.1 + } + }, + "backend": { + "tracing": { + "exporter": "opentelemetry-otlp", + "serviceName": "bitcoin24-api", + "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT:-https://telemetry.example.com}" + }, + "metrics": { + "exporter": "prometheus", + "dashboards": ["grafana/btc-model-latency", "grafana/btc-model-throughput"], + "tracked": [ + { "name": "api_latency_p95", "threshold": 400, "unit": "milliseconds" }, + { "name": "btc_price_job_success", "threshold": 0.99, "unit": "ratio" } + ] + } + } +} diff --git a/docs/authentication_account_persistence.md b/docs/authentication_account_persistence.md new file mode 100644 index 0000000..b6d7f56 --- /dev/null +++ b/docs/authentication_account_persistence.md @@ -0,0 +1,106 @@ +# Authentication & Account Persistence Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +This blueprint spans tasks **6** and **11** of the [Development Plan](./development_plan.md), covering the authentication stack and scenario persistence controls required throughout the product. + +Detailed save/load UX, revision management, and cross-screen behaviors are further elaborated in the [Scenario Persistence Controls](./scenario_persistence_controls.md) guide. + +This document operationalizes the "Implement basic username/password auth" task. It details the back-end modules, database schema, session strategy, front-end UX expectations, and quality guardrails required to deliver secure sign-up/login and scenario persistence for the Bitcoin24 web application, and it underpins the guard behaviors specified in the [Route Guarding & Navigation](./route_guarding_navigation.md) guide. +This document operationalizes the "Implement basic username/password auth" task. It details the back-end modules, database schema, session strategy, front-end UX expectations, and quality guardrails required to deliver secure sign-up/login and scenario persistence for the Bitcoin24 web application. + +## 1. Goals & Principles +- **Security first:** Hash passwords with Argon2id, enforce strong entropy requirements, and ship with rate limiting + anomaly detection. +- **Snappy UX:** Keep account creation < 5s with optimistic navigation, inline validation, and zero full-page reloads. +- **State continuity:** Auth state must hydrate automatically on reload and propagate to scenario stores so flows (BTC → Macro → Models) remain uninterrupted. +- **Extensibility:** Architecture should support future MFA, SSO, and account recovery without rewriting the core auth module. + +## 2. Technology & Service Choices +- **Auth service:** NestJS module (within `/apps/api`) leveraging `@nestjs/passport` + `passport-local` for credential flow and `passport-jwt` for session verification. +- **Password hashing:** `argon2` library with configurable memory/time cost aligned to OWASP 2024 recommendations. +- **Token format:** Short-lived JWT access tokens (15 min) delivered via httpOnly, Secure cookies + Refresh tokens (7 days) stored server-side. +- **Database:** PostgreSQL tables managed via Prisma migrations; Redis optional for session blacklists and rate-limiting counters. +- **Email service (optional future):** Postmark/Resend integration abstracted behind provider interface; not required for MVP but scaffolding ready. + +## 3. Data Model & Storage +| Table | Purpose | Columns | +|-------|---------|---------| +| `users` | Core identity record | `id (uuid)`, `username (unique)`, `email (nullable, unique)`, `password_hash`, `created_at`, `updated_at`, `last_login_at`, `failed_attempts`, `locked_until` | +| `sessions` | Refresh token tracking | `id`, `user_id`, `refresh_token_hash`, `expires_at`, `created_at`, `ip_address`, `user_agent`, `revoked_at` | +| `scenarios` | User-owned scenario snapshots | See [Shared App Foundation](./shared_app_foundation.md) schema for JSON payload | +| `btc_prices` | Historical/live BTC price feed | Shared with pricing service for onboarding defaults | + +- Index `users.username`, `sessions.refresh_token_hash`, and `scenarios.user_id` for fast lookups. +- Store refresh tokens hashed (Argon2) before persistence to mitigate DB leakage risk. + +## 4. API Surface +| Endpoint | Method | Auth | Description | +|----------|--------|------|-------------| +| `/auth/signup` | POST | Public | Accepts `{ username, password, email? }`; validates strength, checks availability, creates user, seeds default scenario, returns session cookies + profile payload. | +| `/auth/login` | POST | Public | Verifies credentials, rotates refresh token, returns session cookies + profile + active scenario summary. | +| `/auth/logout` | POST | Authenticated | Invalidates refresh token (server-side revoke) and clears cookies. | +| `/auth/refresh` | POST | Refresh token | Issues new access token if refresh token valid + not revoked. | +| `/auth/me` | GET | Authenticated | Returns user profile, latest scenario metadata, feature flags. | +| `/scenarios` | CRUD | Authenticated | Covered in [Shared App Foundation](./shared_app_foundation.md); ensure ownership enforcement via `user_id`. | + +- Enforce per-IP rate limits (e.g., 10/min) on `signup`/`login` using NestJS `@nestjs/throttler` or Redis-backed limiter. +- Implement structured error payloads `{ code, message, fieldErrors }` for front-end mapping. + +## 5. Front-End Integration +- **Onboarding wizard:** Reuse forms defined in [Flow-Specific UX Interactions](./flow_specific_ux_interactions.md); call `/auth/signup` or `/auth/login` depending on branch. Display server validation inline. +- **State management:** + - Zustand `useAuthStore` holds `user`, `status`, `sessionExpiresAt`. + - React Query `useAuth` hooks wrap API calls, set cookies via `credentials: 'include'`, and trigger scenario prefetch on success. +- **Persistence:** On login/signup, hydrate scenario store with payload from `/auth/me` + `/scenarios` list; mark active scenario for guided flow. +- **Error handling:** Show toast with retry guidance for network errors; escalate `locked_until` responses into modal that guides to support. +- **Auto-refresh:** Silent refresh triggered by SWR interval (~10 min) or visibility change; fallback to forced relogin if refresh fails. + +## 6. Security Controls +- Enforce password policy: minimum 12 chars, must include uppercase/lowercase/number/symbol; provide strength meter (zxcvbn). +- Lock account for 15 minutes after 5 failed attempts; escalate to support after repeated lockouts. +- Require HTTPS (HSTS) in production; set `SameSite=Lax` cookies. +- Log auth events (signup, login, logout, failure) to audit table and ship to centralized logging (e.g., Datadog). +- Run dependency scanning (npm audit, Snyk) as part of CI for auth module. + +## 7. Scenario Lifecycle & Auto-Save +- Seed a default scenario post-signup using workbook defaults with user-selected BTC starting price. +- Implement optimistic `PUT /scenarios/:id` updates triggered on `Next`/`Back` actions and auto-save interval (every 60s). +- Provide `POST /scenarios` for "Save As" flows and `POST /scenarios/:id/duplicate` convenience route. +- Ensure deletes perform soft-delete (timestamp `deleted_at`) to support restore functionality later. + +## 8. Testing & QA Strategy +- **Unit tests:** + - Validate password hashing, token issuance, and scenario ownership guards using Vitest. + - Mock Prisma to ensure unique constraint violations return friendly errors. +- **Integration tests:** + - Use Supertest to exercise auth endpoints, verifying cookies and refresh workflow. + - Playwright scenarios that cover onboarding wizard → signup → redirect → scenario auto-save. +- **Security tests:** + - Add OWASP ZAP automated scan in CI for auth routes. + - Pen-test checklist covering SQL injection, auth bypass, session fixation. +- **Load tests:** K6 script to simulate burst login attempts ensuring throttling engages. + +## 9. Observability & Operations +- Metrics: login success rate, signup conversion, refresh token failure rate, account lockouts. +- Alerts: trigger when signup error rate > 5% or refresh token failures > 2% for 10 minutes. +- Dashboards: Grafana panels per endpoint latency (p95 < 200ms) and error codes. +- Runbooks: Document recovery steps for locked users, DB outage, or compromised refresh token. + +## 10. Implementation Milestones +1. Scaffold NestJS auth module with DTO validation + Argon2 hashing. +2. Add Prisma models/migrations for `users`, `sessions`, scenario foreign key, and indexes. +3. Implement endpoints + rate limiting + logging middleware. +4. Wire React Query hooks, Zustand store, and onboarding wizard forms. +5. Integrate auto-refresh + global route guards. +6. Build regression + security test suites and add to GitHub Actions workflow. +7. Launch feature behind feature flag; run internal alpha with manual QA before public release. + +## Related Documents +- Visual + tech context: [Design System & Tech Stack](./design_system.md) +- UX flow requirements: [Flow-Specific UX Interactions](./flow_specific_ux_interactions.md) +- Architectural dependencies: [Shared App Foundation](./shared_app_foundation.md) +- Performance & compliance guardrails: [Performance & Accessibility Standards](./performance_accessibility_standards.md) +- Theming/motion integration for auth forms: [Shared Theming & Motion Framework](./theming_motion_framework.md) +- Wizard-specific implementation details: [Onboarding Wizard Implementation Blueprint](./onboarding_wizard.md) +- Post-login experience: [User Home Page Implementation Blueprint](./user_home_page.md) diff --git a/docs/btc_model_screen.md b/docs/btc_model_screen.md new file mode 100644 index 0000000..e02aab2 --- /dev/null +++ b/docs/btc_model_screen.md @@ -0,0 +1,96 @@ +# Bitcoin24 Web App – BTC Model Screen Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #16 – BTC model screen implementation. +- **Dependencies:** Shared calculation engine ([Shared App Foundation Blueprint](./shared_app_foundation.md)), authentication & scenario persistence ([Authentication & Account Persistence](./authentication_account_persistence.md), [Scenario Persistence Controls](./scenario_persistence_controls.md)), pricing infrastructure ([Dynamic Base-Year Handling](./dynamic_base_year_handling.md), [External BTC Price Ingestion](./external_btc_price_ingestion.md)), live price onboarding ([Live Price Onboarding Integration](./live_price_onboarding_integration.md)), guided navigation ([Route Guarding & Navigation](./route_guarding_navigation.md), [Guided Flow Blueprint](./guided_model_flow.md)), and macro outputs ([Macro Model Screen Blueprint](./macro_model_screen.md)). +- **Downstream impact:** Macro and micro/nation screens rely on BTC scenario presets and 2045 KPIs defined here; ensure shared selectors surface these values consistently. + +## Objectives +1. Deliver a modern, high-performance interface for selecting BTC scenarios, editing ARR assumptions, and visualizing 21-year trajectories. +2. Surface the workbook’s headline metrics (price, market cap, asset share) with responsive tables, KPI cards, and charts aligned with the design system. +3. Integrate live price defaults, auto-save, and guided flow cues so users can progress seamlessly into macro and downstream models. + +## Scope +- Scenario presets (Bear, Base, Bull) with editable ARR parameters, 2024/Current price input, and toggles for advanced calculation tables. +- Yearly BTC output table (latest historical year → 2045) including ARR, price, and market cap columns with sticky headers and CSV export. +- KPI band for 2045 price, market cap, asset share, and ARR summary. +- Dual chart suite: combo line/bar trajectory (price & market cap) and 2045 market-cap comparison bars (BTC vs. benchmark assets). +- Integration with shared state, auto-save, and navigation actions (“Next: Macro Model”, “Back: Home”). + +## Non-Goals +- Re-implementing macro calculations; this screen consumes outputs from shared services. +- Managing micro/nation scenario comparisons beyond providing BTC assumptions. +- Detailing authentication UX beyond leveraging existing onboarding & route guards. + +## User Experience & Layout +- **Global chrome:** Authenticated shell with breadcrumb `Home / BTC Model` and guided badge “Step 1 of 3”. +- **Hero summary band:** Sticky top row of four KPI cards (Current Price, 2045 Price, 2045 Market Cap, BTC Share of Global Assets) pulling from shared selectors; include timestamp for live price source. +- **Scenario + inputs panel:** + - Preset pills for Bear/Base/Bull with preview of ARR & price assumptions. + - Optional dropdown for custom saved scenarios (per scenario persistence guide). + - Editable fields: Current price (prefilled from live price service), 2025 ARR (or start-year ARR), annual reduction %, steady-state ARR target, steady-state year. + - Toggle to reveal scenario matrix table (workbook columns E–G) for advanced users; collapsible with smooth motion per theming guide. +- **Results workspace:** + - Yearly table using responsive virtualized grid; columns include Year, ARR, BTC Price, BTC Market Cap, Additional Notes (e.g., milestone callouts). Provide inline sparkline option for quick trend view. + - Chart row with two cards: (1) 21-year trajectory combo chart (price line, market-cap bars), (2) 2045 market-cap comparison bar chart with micro-interactions and tooltips. + - Insight drawer summarizing workbook commentary (e.g., ARR trends, asset share interpretation) with inline tooltips. +- **Actions:** Primary “Next: Macro Model” button, secondary “Save Scenario”, tertiary “Duplicate Scenario”, contextual “Reset to Preset” and “Download CSV”. + +## Data & State Requirements +- Fetch scenario defaults from shared configuration module; include preset metadata (name, description, colors). +- Bind inputs to centralized BTC slice; update local state optimistically then trigger recalculation pipeline. +- Incorporate live price selection from onboarding/home flows; lock field when user opts into automatic daily updates. +- Align year axis with dynamic start year; ensure historical data (<= current year) displayed as read-only rows (italicized) and future years editable via assumptions. +- Expose selectors for 2045 KPIs and charts to other screens (e.g., macro screen referencing 2045 price). + +## Validation & Error Handling +- Enforce numeric bounds (e.g., ARR between -50% and 500%, reduction between 0% and 50%). +- Warn users when manual overrides diverge significantly (>±20%) from presets; offer revert option. +- Show inline validation states (color-coded borders, accessible messages) and disable “Next” when critical fields invalid. +- Handle calculation failures with inline alert, retry button, and fallback to last known successful projection. + +## Accessibility Considerations +- Ensure preset pills and toggles are keyboard navigable with clear focus rings per theming guide. +- Provide descriptive aria labels for charts (e.g., `aria-describedby` pointing to hidden data tables). +- Maintain WCAG AA contrast for text over gradients; offer textual fallback for KPI values. +- Announce auto-save success/failure through polite live regions. + +## Performance Considerations +- Lazy load chart modules; prefetch macro screen bundle when user dwells on “Next”. +- Virtualize yearly table and memoize derived data to avoid expensive recalculations. +- Batch state updates when editing multiple fields rapidly; debounce recalculation to ~750ms. +- Cache preset data client-side and revalidate in background to keep interactions snappy. + +## Analytics & Telemetry +- Track events: preset selection, manual override toggles, chart interactions, CSV downloads, Next/Back usage. +- Measure time-to-first-calculation, auto-save latency, and error frequency. +- Emit breadcrumbs for scenario changes to aid debugging in observability stack. + +## Testing Strategy +- **Unit tests:** reducers/selectors for BTC state, validation helpers, KPI formatting utilities, component rendering. +- **Integration tests:** Playwright flow covering preset selection, manual edits, live price acceptance, auto-save, navigation to Macro screen. +- **Visual regression:** Snapshot hero band, tables, charts across light/dark modes. +- **Accessibility tests:** Axe scans and keyboard-only traversal; verify screen reader announcement of chart summaries. +- **Performance tests:** Lighthouse CI focusing on interaction latency; measure update time under rapid input changes. + +## Security & Privacy +- Ensure scenario data mutations require valid auth token; protect live price endpoints with rate limiting. +- Sanitize user-entered notes or custom scenario names before persistence. +- Avoid exposing raw live price API keys to client; proxy through backend service. + +## Rollout Plan +1. Implement layout with mocked data and motion specs; review against design system. +2. Integrate live price defaults and dynamic start-year data; verify historical rows lock correctly. +3. Wire recalculation engine and scenario persistence with optimistic updates. +4. Add charts and KPI cards; align styling with theming/motion guide. +5. Execute testing plan (unit, integration, visual, accessibility, performance). +6. Conduct stakeholder walkthrough comparing against Excel BTC sheet; adjust assumptions/labels as needed. +7. Release alongside macro screen to validate guided flow end-to-end; monitor telemetry for regressions. + +## Open Questions +- Should users be able to create additional presets beyond Bear/Base/Bull, and how are they shared across accounts? +- Do we surface BTC dominance targets or other metrics beyond workbook scope (e.g., stock-to-flow)? +- What archival strategy do we need for historical scenario versions when auto-save is enabled? + diff --git a/docs/corporate_micro_model_screen.md b/docs/corporate_micro_model_screen.md new file mode 100644 index 0000000..3946c52 --- /dev/null +++ b/docs/corporate_micro_model_screen.md @@ -0,0 +1,96 @@ +# Bitcoin24 Web App – Corporate Micro Model Screen Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #18 – Corporate micro model screen implementation. +- **Upstream dependencies:** Macro/BTC outputs ([Macro Model Screen Blueprint](./macro_model_screen.md), [BTC Model Screen Blueprint](./btc_model_screen.md)), shared calculation engine ([Shared App Foundation Blueprint](./shared_app_foundation.md)), authentication & persistence ([Authentication & Account Persistence](./authentication_account_persistence.md), [Scenario Persistence Controls](./scenario_persistence_controls.md)), guided navigation ([Guided Flow Blueprint](./guided_model_flow.md), [Route Guarding & Navigation](./route_guarding_navigation.md)), pricing infrastructure ([Dynamic Base-Year Handling](./dynamic_base_year_handling.md), [External BTC Price Ingestion](./external_btc_price_ingestion.md)), and onboarding/live price flows ([Onboarding Wizard Blueprint](./onboarding_wizard.md), [Live Price Onboarding Integration](./live_price_onboarding_integration.md)). +- **Downstream impact:** Institution ([Institution Micro Model Screen Blueprint](./institution_micro_model_screen.md)) and nation-state blueprints will reuse treasury, debt, and comparison components validated here; shared UI library (task 21) should uplift successful patterns from this screen. + +## 1. Objectives +1. Provide a high-fidelity corporate treasury modeling workspace that mirrors the Excel "Corporate" sheet while embracing the Bitcoin24 design system. +2. Allow users to toggle between strategy presets (e.g., Legacy Treasury, Hybrid, BTC Maxi) and customize corporate financial assumptions with immediate recalculation feedback. +3. Surface 21-year projections, 2045 comparisons, and chart visualizations that clarify how treasury conversion, debt usage, and share issuance affect long-term outcomes. +4. Maintain performance, accessibility, and responsiveness standards so finance teams can interrogate dense data tables across devices. + +## 2. Scope +- Strategy selector with preset cards for the five workbook strategies plus slots for saved custom scenarios. +- Assumptions workspace covering revenue/cash flow, growth rates, valuation multiples, share dilution, treasury allocation, BTC conversion percentages, leverage, and debt terms. +- Annual results tables: income statement highlights, share price/market cap trajectory, treasury asset composition, BTC purchases (cash flow vs. debt), debt schedules, and share count changes. +- 2045 comparison grid summarizing share price, market cap, BTC holdings, treasury allocation mix, and CAGR for all strategies. +- Chart suite: (1) 2045 share price bar chart, (2) 2045 BTC treasury bar chart, (3) 21-year share price vs. BTC holdings combo chart mirroring Excel chart ranges. +- Guided navigation footer with Back/Next actions, scenario persistence controls, CSV export, and validation summary. + +## 3. Non-Goals +- Re-implementing individual/institution/nation-specific fiscal logic (handled in their own tasks). +- Designing bespoke analytics dashboards beyond the prescribed tables/charts. +- Building advanced collaboration features (e.g., multi-user editing) in this iteration. + +## 4. Data & State Dependencies +- Consume normalized corporate model payloads from calculation API (`/models/corporate`) keyed by strategy and scenario. +- Ingest macro context (start year, BTC price path, ARR) for consistent calculations and banners. +- Persist user edits through scenario persistence layer with optimistic updates and conflict resolution. +- Respect guided flow context to understand whether user arrived from Macro or Home screen. + +## 5. User Experience & Layout +- **Global shell:** Authenticated layout with breadcrumb `Home / Guided Flow / Corporate Model`, step badge showing either "Step 3 of 3" (guided flow) or "Corporate Model" when accessed directly. +- **Hero KPI strip:** Cards summarizing Current Year Share Price, 2045 Share Price, 2045 BTC Treasury, BTC % of Treasury, and Equity CAGR. Include delta badges vs. baseline strategy. +- **Strategy rail:** Horizontal slider/pills with preset details (name, BTC allocation summary, risk tag). Provide "Custom" slot tied to saved variants. +- **Assumptions workspace:** + - Collapsible sections: Company Profile (revenue/cash flow, growth, margins), Treasury Policy (cash allocation, BTC conversion, issuance), Capital Structure (debt terms, leverage caps), Equity Actions (buybacks/issuance), Tax & Expense assumptions. + - Inline tooltips referencing glossary definitions and linking to supporting docs. + - Input formatting for currency, percentages, multipliers; enforce accessible labels and descriptions. +- **Results workspace:** + - Tabbed interface: Annual Projections, 2045 Snapshot, Sensitivity (future extension placeholder). + - Annual Projections table grouped by category with frozen first column, virtualized rows, and sticky column summaries for quick scanning. + - 2045 Snapshot table compares strategies using highlight states for selected strategy and saved custom scenarios. +- **Charts row:** Responsive grid containing the three required charts with shared legend alignment, toggle for logarithmic scaling, and downloadable PNG/CSV actions. +- **Insight drawer:** Optional right rail summarizing key insights (e.g., dilution impact, leverage ratio warnings) and referencing assumptions. +- **Action footer:** Persistent controls for Back (to Macro), Next (to guided completion or Home), Save, Save As, Duplicate, Delete, Export CSV, and aggregated validation messages. + +## 6. Interaction & Behavior Requirements +1. **Preset switching:** Instant feedback (<200ms) applying preset defaults with optimistic skeleton states; confirm before discarding unsaved edits. +2. **Scenario management:** Integrate dropdown for saved corporate scenarios with rename/delete; auto-save after debounce (1.5s) and show toast confirmations. +3. **Validation:** Enforce percentage ranges (0–100%), leverage caps, non-negative treasury balances, and share issuance limits. Surface inline errors and summary list in footer. +4. **Debt handling:** When debt-to-buy-BTC toggles on, reveal additional controls (issuance schedule, interest rate) and update tables/charts in real time. +5. **Guided flow integration:** Completion of required fields enables Next button; show progress state when returning to Macro or Home. Preserve unsaved edits via local draft store. +6. **Historical pricing context:** Banner indicates start year and live price used; "Adjust" link opens shared price selector component. +7. **Chart/table synchronization:** Hovering table rows highlights relevant chart series; keyboard navigation supported via focusable data cells and ARIA descriptions. +8. **Accessibility:** Provide semantic headings, region landmarks, and skip links. Ensure color contrast for delta chips meets WCAG AA. + +## 7. Performance Considerations +- Virtualize annual projection tables; memoize derived datasets and leverage React Suspense + skeleton loaders for asynchronous fetches. +- Prefetch corporate model data when user completes Macro step; cache results using React Query with background refetching. +- Defer heavy chart rendering until container is visible (Intersection Observer) to keep initial load snappy. +- Batch persistence updates and throttle analytics events to avoid network congestion during rapid edits. + +## 8. Security & Compliance +- Protect API calls with authenticated tokens/cookies per route guarding blueprint. +- Mask sensitive corporate identifiers in telemetry; store only aggregated metrics needed for analytics. +- Validate server-side inputs to prevent injection or malicious payloads; enforce rate limits on scenario CRUD endpoints. + +## 9. Testing Strategy +- **Unit tests:** Validate reducers/selectors, validation utilities, and chart data transformers via Vitest. +- **Component tests:** React Testing Library coverage for preset switching, validation messaging, debt toggle interactions, and scenario persistence prompts. +- **Integration tests:** Playwright flows for guided navigation (Macro → Corporate → Save → Return) and for scenario lifecycle (create, duplicate, delete). +- **Visual regression:** Storybook snapshots for hero KPIs, assumption panels, tables, and charts in light/dark themes. +- **Performance tests:** Lighthouse CI thresholds on this route (TTI, LCP) plus React profiler checks for table virtualization. + +## 10. Analytics & Telemetry +- Track events: corporate_preset_selected, corporate_assumption_edited, corporate_scenario_saved, debt_toggle_changed, chart_exported, validation_error_shown. +- Capture performance metrics (render_time, table_scroll_latency) and scenario outcome deltas for product analytics. +- Log guided flow progression (step_completed) with timestamps to identify friction points. + +## 11. Rollout Plan +1. Build assumption panel, KPI cards, and chart components in Storybook; validate against design specs. +2. Integrate API hooks and state slices; implement presets and scenario persistence, behind feature flag if needed. +3. QA data accuracy by reconciling against Excel corporate sheet outputs for baseline strategies. +4. Conduct accessibility review (keyboard navigation, screen reader labels) and performance tuning. +5. Release to staging, monitor analytics/feedback, then graduate to production with progressive rollout. + +## 12. Open Questions +- Should we support importing corporate financials via CSV to seed assumptions, or manual entry only for v1? +- Do we need role-based access (e.g., corporate vs. advisor) to restrict certain controls such as share issuance toggles? +- How should we handle scenarios where valuation multiple inputs imply negative share price outcomes—warn or hard-block? + +Refer to the [Design System & Tech Stack](./design_system.md), [Theming & Motion Framework](./theming_motion_framework.md), [Flow-Specific UX Interactions](./flow_specific_ux_interactions.md), [Performance & Accessibility Standards](./performance_accessibility_standards.md), and [Shared App Foundation Blueprint](./shared_app_foundation.md) for global design, technical, and quality guardrails. diff --git a/docs/design_system.md b/docs/design_system.md new file mode 100644 index 0000000..00a30e5 --- /dev/null +++ b/docs/design_system.md @@ -0,0 +1,98 @@ +# Bitcoin Model Web App – Design System & Tech Stack + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +This guide fulfills task **1** of the [Development Plan](./development_plan.md) and establishes visual precedents referenced by the shared component initiative (task **21**). + +## 1. UX Vision +- **Inspiration:** Mirror the sleek, dark-mode aesthetic of Microstrategist with high-contrast typography, glassmorphism cards, and cinematic hero imagery. +- **Tone:** Professional, data-forward, and trustworthy while remaining approachable for power users and newcomers. +- **Key Experiences:** + - Fast, animated onboarding that introduces the app's value proposition. + - Data exploration surfaces with sticky summaries, collapsible deep dives, and responsive charts. + - Guidance overlays and contextual tooltips to support first-time users. + +## 2. Visual Language Foundations +### Color Palette +| Token | Hex | Usage | +|-------|-----|-------| +| `bg.base` | #0B0E11 | Primary background for dark theme. +| `bg.surface` | rgba(25, 32, 40, 0.85) | Translucent card surfaces (glass effect). +| `accent.primary` | #F7931A | Bitcoin brand color for CTAs, key data points, and chart highlights. +| `accent.secondary` | #2DD4BF | Complementary highlight for positive trends and active states. +| `accent.warning` | #F59E0B | Risk alerts and cautionary copy. +| `accent.error` | #F87171 | Validation errors. +| `text.primary` | #F8FAFC | Main copy, high contrast. +| `text.secondary` | #94A3B8 | Secondary copy labels. +| `border.subtle` | rgba(148, 163, 184, 0.2) | Card borders, dividers. + +Include light-theme variants for accessibility, but default to dark. + +### Typography +- **Display:** `Space Grotesk` (bold) for hero headings, numbers, and KPI metrics. +- **Body:** `Inter` for copy, tables, and forms. +- **Code/Mono:** `JetBrains Mono` for formula snippets. +- Implement responsive scale with clamp-based CSS to keep readability across devices. + +### Iconography & Imagery +- Custom line icons derived from BTC motifs (nodes, blocks) using duotone accent colors. +- Hero/section backgrounds with subtle animated particle shaders or gradient noise. + +## 3. Layout & Interaction Patterns +- **Grid:** 12-column responsive grid with 24px base gutter. Collapse to stacked layout below 768px. +- **Cards:** Glassmorphism cards with backdrop blur (12px) and subtle drop shadows (`0 20px 45px rgba(0,0,0,0.35)`). +- **Navigation:** + - Persistent top app bar with logo, breadcrumbs, and user menu. + - Secondary left rail for quick switching between BTC, Macro, and user-specific models. +- **Motion:** + - Use spring easing (e.g., Framer Motion `type: "spring"`, `stiffness: 120`, `damping: 20`) for route transitions. + - Micro-interactions: hover lifts, button ripple, loading skeleton shimmer. +- **Tables & Charts:** + - Sticky headers and columns for long data tables. + - Toggle panels for advanced calculations to reduce initial cognitive load. + +## 4. Accessibility & Responsiveness +- Target WCAG 2.1 AA contrast ratios (ensure text on glass surfaces remains 4.5:1+). +- Keyboard navigable modals and forms; focus states use accent outlines. +- Provide high-contrast mode toggle and respect prefers-reduced-motion to disable heavy animations. + +## 5. Core Technology Stack +### Front End +- **Framework:** Next.js 14 with React 18 + TypeScript for hybrid SSR/SSG and built-in routing. +- **Styling:** Tailwind CSS with custom design tokens + CSS variables for theming; apply `tailwindcss-animate` for motion primitives. +- **Animation:** Framer Motion for route transitions, staggered lists, and onboarding wizard. +- **State Management:** Zustand for lightweight global state (auth, scenarios, pricing) complemented by React Query for server cache. +- **Charts:** ECharts (dark-mode friendly, high-performance) wrapped in reusable chart components. +- **Forms:** React Hook Form + Zod for validation, aligning with input guardrails. + +### Back End +- **Runtime:** Node.js 20 + TypeScript using NestJS for modular architecture or Fastify for lighter footprint. +- **Database:** PostgreSQL (hosted via Supabase or Neon) for users, scenarios, and price history. +- **Auth:** Supabase Auth or custom NestJS module with JWT/argon2 hashing and httpOnly cookies. +- **Data Pipelines:** Scheduled serverless function (Vercel Cron or Supabase Edge) fetching BTC prices from CoinGecko API, writing to `btc_prices` table. + +### Infrastructure & DevOps +- Deploy front end via Vercel for edge caching and preview deployments. +- Host backend on Fly.io or Render with autoscaling, exposing REST/GraphQL endpoints. +- Use Prisma ORM for schema management and migrations. +- CI/CD with GitHub Actions running linting, type checks, unit tests, and Lighthouse/Axe audits. + +## 6. Tooling & Collaboration +- **Design:** Figma library capturing tokens, components, and interaction specs. +- **Documentation:** Storybook for interactive component gallery; Docusaurus for developer docs. +- **Analytics & Monitoring:** Vercel Analytics + Sentry for frontend; OpenTelemetry for backend metrics. +- **Implementation Guide:** Refer to [docs/theming_motion_framework.md](./theming_motion_framework.md) for concrete Tailwind configuration steps, motion presets, and responsive layout utilities derived from this design system. +- **Flow Behaviors:** Consult [docs/flow_specific_ux_interactions.md](./flow_specific_ux_interactions.md) for onboarding, navigation, and scenario-flow UX patterns that complement these visual foundations. +- **Onboarding Wizard:** Pair the flow guidance with [docs/onboarding_wizard.md](./onboarding_wizard.md) for implementation details on the Get Started journey and price selection step. +- **Architecture & Data Layer:** Pair this guide with [docs/shared_app_foundation.md](./shared_app_foundation.md) to understand how the monorepo, services, and calculation engine operationalize the design vision. +- **Authentication & Persistence:** Coordinate with [docs/authentication_account_persistence.md](./authentication_account_persistence.md) so account flows and saved scenarios mirror the visual language and UX expectations set here. +- **Home Experience:** Reference the [User Home Page Implementation Blueprint](./user_home_page.md) for detailed layout, interaction, and performance goals of the authenticated landing hub. +- **Performance & Accessibility:** Follow [docs/performance_accessibility_standards.md](./performance_accessibility_standards.md) to uphold the speed, inclusivity, and observability targets that make the experience feel polished in practice. + +## 7. Success Metrics +- LCP < 2s on mid-tier devices, CLS < 0.1, accessibility score ≥ 95. +- Auth signup-to-first-scenario completion rate > 70%. +- Daily BTC price sync success ≥ 99% with automated alerts for failures. + +This design system and stack blueprint ensures the Bitcoin model web app delivers a visually striking, performant, and maintainable experience aligned with the inspiration source. diff --git a/docs/development_plan.md b/docs/development_plan.md new file mode 100644 index 0000000..8610b36 --- /dev/null +++ b/docs/development_plan.md @@ -0,0 +1,72 @@ +# Bitcoin24 Development Plan + +[Back to README](../README.md) + +## Overview +This document is the authoritative roadmap for implementing the Bitcoin24 web application. It consolidates every scoped task, groups related efforts, and links to the deep-dive guides that describe how each initiative should be executed. All feature, UX, and infrastructure work should trace back to the initiatives catalogued below. + +## Task Index +| # | Initiative | Description | Status | Key References | +|---|------------|-------------|--------|----------------| +| 1 | Modern design system & tech stack | Establish visual direction, component primitives, and platform stack inspired by Microstrategist. | ✅ Completed – blueprint authored. | [Design System & Tech Stack](./design_system.md) | +| 2 | Shared theming & motion framework | Implement Tailwind tokens, animation presets, and responsive utilities that deliver the design system. | ✅ Completed – implementation guide drafted. | [Theming & Motion Framework](./theming_motion_framework.md) | +| 3 | Flow-specific UX interactions | Define onboarding, navigation, live price handling, and data exploration behaviors. | ✅ Completed – interaction plan documented. | [Flow-Specific UX Interactions](./flow_specific_ux_interactions.md) | +| 4 | Performance, accessibility, & observability guardrails | Set budgets, tooling, and monitoring to keep the experience fast and inclusive. | ✅ Completed – standards guide published. | [Performance & Accessibility Standards](./performance_accessibility_standards.md) | +| 5 | Shared application foundation | Scaffold repository, ingest workbook data, and stand up calculation engines and services. | ✅ Completed – architectural blueprint delivered. | [Shared App Foundation Blueprint](./shared_app_foundation.md) | +| 6 | Authentication and account persistence | Implement username/password auth, session management, and scenario storage. | ✅ Completed – implementation blueprint documented. | [Authentication & Account Persistence](./authentication_account_persistence.md) | +| 7 | Onboarding wizard | Build the Get Started flow that guides users through account creation and setup. | ✅ Completed – onboarding spec drafted. | [Onboarding Wizard Blueprint](./onboarding_wizard.md) | +| 8 | User home experience | Deliver the authenticated dashboard with saved scenarios and guided navigation. | ✅ Completed – home experience blueprint produced. | [User Home Page Blueprint](./user_home_page.md) | +| 9 | Route guarding & global navigation | Enforce authenticated access and cohesive navigation patterns. | ✅ Completed – guard & navigation blueprint documented. | [Route Guarding & Navigation](./route_guarding_navigation.md) | +| 10 | Guided BTC → Macro → model flow | Surface step indicators, auto-save, and navigation cues across modeling screens. | ✅ Completed – guided flow blueprint published. | [Guided Flow Blueprint](./guided_model_flow.md) | +| 11 | Scenario persistence controls | Provide save/load/duplicate functionality tied to user accounts. | ✅ Completed – blueprint documented. | [Scenario Persistence Controls](./scenario_persistence_controls.md) | +| 12 | Dynamic base-year handling | Shift projections to use the latest historical BTC data instead of static 2025 assumptions. | ✅ Completed – engineering spec documented. | [Dynamic Base-Year Handling](./dynamic_base_year_handling.md) | +| 13 | External BTC price ingestion | Fetch daily prices, persist them, and expose defaults to the UI. | ✅ Completed – ingestion blueprint documented. | [External BTC Price Ingestion](./external_btc_price_ingestion.md) | +| 14 | Live price onboarding integration | Let users accept live prices or choose alternatives during onboarding and in models. | ✅ Completed – integration blueprint published. | [Live Price Onboarding Integration](./live_price_onboarding_integration.md) | +| 15 | Macro model screen | Implement controls, tables, charts, and collapsible calculations for the macro layer. | ✅ Completed – blueprint documented. | [Macro Model Screen Blueprint](./macro_model_screen.md) | +| 16 | BTC model screen | Build scenario presets, yearly outputs, KPIs, and charts for the BTC sheet. | ✅ Completed – blueprint documented. | [BTC Model Screen Blueprint](./btc_model_screen.md) | +| 17 | Individual micro model screen | Translate individual strategy table, forecasts, comparisons, and charts. | ✅ Completed – blueprint documented. | [Individual Micro Model Screen Blueprint](./individual_micro_model_screen.md) | +| 18 | Corporate micro model screen | Deliver treasury strategy inputs, results tables, and visualization suite. | ✅ Completed – blueprint documented. | [Corporate Micro Model Screen Blueprint](./corporate_micro_model_screen.md) | +| 19 | Institution micro model screen | Implement portfolio assumptions, annual results, and chart trio. | ✅ Completed – blueprint documented. | [Institution Micro Model Screen Blueprint](./institution_micro_model_screen.md) | +| 20 | Nation-state model screens | Build indebted, wealthy, and US nation experiences with fiscal levers and charts. | ✅ Completed – blueprint documented. | [Nation-State Model Screens Blueprint](./nation_state_model_screens.md) | +| 21 | Shared UI component library | Extract reusable cards, tables, collapsibles, and chart wrappers. | ✅ Completed – component library blueprint published. | [Shared UI Component Library Blueprint](./shared_ui_component_library.md) | +| 22 | Validation, guidance, & analytics | Enforce input rules, contextual help, and telemetry across the app. | ✅ Completed – validation & analytics blueprint documented. | [Validation, Guidance, & Analytics Blueprint](./validation_guidance_analytics.md) | + +> **Note:** Items marked "Upcoming" will receive dedicated implementation briefs as the project advances. Until then, use this +table to track sequencing, dependencies, and ownership discussions. + +## Milestone Groupings +### Foundation Milestone +Tasks 1–5 establish the visual, experiential, and architectural baseline. Complete these before beginning any feature +implementation to keep subsequent work aligned. + +### Access & Onboarding Milestone +Tasks 6–11 build the authentication system, onboarding flow, and scenario persistence. These unlock the primary user journey and +should be prioritized immediately after the foundation. + +### Dynamic Pricing Milestone +Tasks 12–14 integrate historical pricing logic and live data ingestion. Finish these before implementing the modeling screens so +all downstream calculations rely on the same time-aware pricing. + +### Modeling Experience Milestone +Tasks 15–20 cover the BTC, macro, micro, and nation-state experiences. Build them sequentially so shared calculations and UI +components can be reused efficiently. + +### Shared Components & Quality Milestone +Tasks 21–22 consolidate UI primitives and apply validation, guidance, and analytics layers to the full app surface. + +## Usage Guidelines +- Treat this document as the single source of truth for scope prioritization. Update it whenever new work is approved or tasks are + reprioritized. +- Each supporting guide (linked above) should reference this plan to keep context in sync. +- When opening issues or PRs, cite the corresponding task number from this plan. + +## Related Documents +- [Design System & Tech Stack](./design_system.md) +- [Theming & Motion Framework](./theming_motion_framework.md) +- [Flow-Specific UX Interactions](./flow_specific_ux_interactions.md) +- [Performance & Accessibility Standards](./performance_accessibility_standards.md) +- [Shared App Foundation Blueprint](./shared_app_foundation.md) +- [Authentication & Account Persistence](./authentication_account_persistence.md) +- [Onboarding Wizard Blueprint](./onboarding_wizard.md) +- [User Home Page Blueprint](./user_home_page.md) + diff --git a/docs/dynamic_base_year_handling.md b/docs/dynamic_base_year_handling.md new file mode 100644 index 0000000..6724a53 --- /dev/null +++ b/docs/dynamic_base_year_handling.md @@ -0,0 +1,119 @@ +# Bitcoin24 Web App – Dynamic Base-Year Handling + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #12 – Dynamic base-year handling. +- **Dependencies:** Shared data ingestion & calculation services ([Shared App Foundation](./shared_app_foundation.md)), scenario persistence & auth ([Authentication & Account Persistence](./authentication_account_persistence.md); [Scenario Persistence Controls](./scenario_persistence_controls.md)), guided flow UX ([Guided Model Flow](./guided_model_flow.md)), live price onboarding experience ([Live Price Onboarding Integration](./live_price_onboarding_integration.md)), and performance guardrails ([Performance & Accessibility Standards](./performance_accessibility_standards.md)). +- **Downstream impact:** BTC, Macro, micro, and nation-state models; onboarding wizard; home dashboard; charts and KPI cards; live price integration ([External BTC Price Ingestion](./external_btc_price_ingestion.md); [Live Price Onboarding Integration](./live_price_onboarding_integration.md)). + +## Objectives +1. Replace the static 2025 assumption grid with a time-aware price backbone anchored to the latest historical BTC close. +2. Preserve immutable historical values for completed years while recalculating forward-looking projections from the detected base year. +3. Provide consistent time axes, ARR/CAGR calculations, and chart inputs across all modules despite rolling base years. +4. Maintain save/load integrity so persisted scenarios reopen with the same base-year context even as new historical data arrives. + +## Scope +- Historical BTC price storage, indexing, and versioning inside the application data layer. +- Calculation engine adjustments to derive start year and projection windows dynamically. +- API responses that surface the current base year, last historical date, and aligned time vectors to the front end. +- Migration strategy for existing scenarios (pre-dynamic launch) to adopt the new pricing backbone. +- UI cues informing users when historical vs. projected data is displayed (shared with UX docs). + +## Non-Goals +- Implementing external price fetching (covered by task #13 spec). +- Redesigning charts/visual styling beyond what's required to handle variable ranges. +- Adding volatility modeling or alternative asset data. + +## Functional Requirements +1. **Historical price ledger** + - Maintain a table keyed by `date` (UTC) with `close_usd`, `source`, and `ingested_at` metadata. + - Seed ledger with at least 2010-01-01 → current day historical prices from a vetted source. + - Guarantee uniqueness per date; conflict resolution should favor the most recently ingested value while retaining audit history. +2. **Base-year discovery** + - Determine the base year as `max(historical_date).year`. + - Expose the base year, latest price date, and close value via a `/pricing/base-year` endpoint and shared server utility. + - Support override for regression tests (e.g., force base year) via environment flag. +3. **Projection window alignment** + - Dynamically build the year vector as `[base_year, base_year + 1, …, base_year + 20]` for the standard 21-year horizon. + - Retain historical data points for years < base_year (e.g., show 2024 actuals when base year is 2025) but mark them as “locked/historical.” + - Ensure CAGR/ARR calculations use historical actuals as the year-0 anchor and operate over the dynamic horizon. +4. **Model recalibration** + - Update macro & BTC engines to pull starting price and ARR seeds from the historical ledger rather than workbook constants. + - Adjust micro/nation models to query the shared year vector and base-year price before applying their own assumptions. + - Guarantee cross-sheet references (e.g., Macro feeds BTC, BTC feeds micro) are versioned off the same base year to prevent drift. +5. **Scenario persistence compatibility** + - When saving a scenario, store the base year and `historical_price_snapshot` (date + price) used during calculation. + - On reload, if the snapshot matches current ledger, proceed normally; if ledger has advanced, prompt user to opt-in to rebase scenario or continue with archived snapshot. +6. **Data migration** + - Provide a script to migrate legacy scenarios (without base-year metadata) by defaulting to the historical snapshot nearest their original start price. +7. **Observability & alerts** + - Emit metrics/logs when base year advances (new calendar year) or when ledger gaps are detected. + - Alert engineering if ledger lacks data for the current day by market close (configurable SLA). + +## Data Model & Storage Strategy +- **`pricing_daily` table** (PostgreSQL / Supabase): + - `date` DATE PRIMARY KEY. + - `close_usd` NUMERIC(18,2). + - `source` TEXT. + - `ingested_at` TIMESTAMP WITH TIME ZONE DEFAULT `NOW()`. + - `checksum` CHAR(32) for data integrity. +- **`pricing_daily_audit` table** to capture overwritten entries with `version_id`, `date`, `close_usd`, `source`, `ingested_at`, `replaced_at`. +- Add composite index on `(date DESC)` for fast latest lookup. +- Provide SQL view `pricing_latest` returning the single most recent record. + +## Calculation Engine Updates +- Extend shared calculation service (`@bitcoin24/core/pricing`) with: + - `getLatestHistoricalPoint()` → `{ date, closeUsd, baseYear }`. + - `buildYearAxis({ horizon })` → `number[]` using base year. + - `getHistoricalSeries({ yearsBack })` → array of `{ year, value }` for context charts. +- Modify macro/BTC modules to accept `baseYear` and `startPrice` as required parameters; throw explicit error if missing. +- Introduce regression tests ensuring: + - When the ledger’s latest entry rolls to Jan 1 of a new year, year axis shifts accordingly and calculations remain continuous. + - Replaying historical data (e.g., last day of prior year) locks base year to prior year for reproducible scenario testing. + +## API Surface +- `GET /api/pricing/base-year` – returns `{ baseYear, latestDate, latestPrice, historicalContextRange }`. +- `GET /api/pricing/historical?start=YYYY-MM-DD&end=YYYY-MM-DD` – paginated historical series for chart overlays. +- `POST /api/pricing/snapshot` – (internal) capture scenario snapshot; accepts optional `scenarioId` to tie to persistence. +- All endpoints require auth for write operations; read endpoints can be cached publicly for marketing pages that show charts. + +## Front-End Integration +- Global state slice `pricing` stores `baseYear`, `latestPrice`, `latestDate`, `historicalSeries` (lightweight) and emits events when base year changes. +- Provide React hooks `useBaseYear()` and `useHistoricalPrice(date)` to consume data without recalculating in each screen. +- Update onboarding and home dashboards to fetch base year on mount and display “Projections begin from {baseYear} using ${latestPrice} close recorded on {latestDate}.” +- Charts/tables must visually differentiate historical vs. projected years (e.g., using muted colors or pattern fills as outlined in the [Flow-Specific UX Interactions](./flow_specific_ux_interactions.md) guide). + +## UX & Copy Considerations +- Add tooltip copy explaining that historical data is locked and new projections will auto-adjust when the calendar advances. +- When base year shifts, display toast/banner summarizing change and offering to update saved scenarios. +- Ensure accessibility by conveying historical/projected distinction via icons and aria labels, not color alone. + +## Performance Considerations +- Cache `/api/pricing/base-year` responses at the edge (60s TTL) to reduce repeated DB hits. +- Lazy-load large historical series only when charts request extended ranges. +- Validate that year-axis recalculations do not trigger unnecessary re-renders; leverage memoized selectors. + +## Security & Compliance +- Restrict write access (seeding/migrations) to service accounts. +- Validate inputs on historical ingestion to avoid SQL injection or malformed data. +- Log administrative actions in audit trail with user IDs for compliance. + +## Testing Strategy +- **Unit tests:** pricing utilities, year-axis builders, macro/BTC modules verifying dynamic base-year handling. +- **Integration tests:** API endpoints returning expected base year; scenario save/load flows maintain snapshot metadata. +- **Regression tests:** Re-run workbook parity suite using frozen historical ledger to confirm outputs unchanged vs. Excel baseline. +- **End-to-end tests:** Simulate calendar rollover by injecting new historical record and verifying UI updates without manual refresh. + +## Delivery Milestones +1. **Data layer setup** – create tables, seed historical data, expose base-year utility. +2. **Calculation updates** – refactor macro/BTC engines and dependent services to require base-year inputs. +3. **Scenario migration** – backfill existing saved scenarios with base-year metadata and validate reopen flows. +4. **Front-end wiring** – integrate base-year hooks across onboarding, home, BTC, Macro, and micro/nation screens. +5. **QA & rollout** – execute regression and end-to-end tests; monitor metrics during first calendar rollover. + +## Open Questions +- What historical data source should be treated as canonical (e.g., CoinMetrics vs. CoinGecko) and how often should we reconcile discrepancies? +- Do we need to support intraday manual overrides (e.g., user inputs a custom price before official daily close)? +- Should scenarios be allowed to “freeze” on an older base year indefinitely, or do we enforce upgrades after a grace period? + diff --git a/docs/external_btc_price_ingestion.md b/docs/external_btc_price_ingestion.md new file mode 100644 index 0000000..455d1cb --- /dev/null +++ b/docs/external_btc_price_ingestion.md @@ -0,0 +1,121 @@ +# Bitcoin24 Web App – External BTC Price Ingestion + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #13 – External BTC price ingestion. +- **Dependencies:** Shared app foundation ([Shared App Foundation](./shared_app_foundation.md)), dynamic base-year handling ([Dynamic Base-Year Handling](./dynamic_base_year_handling.md)), authentication ([Authentication & Account Persistence](./authentication_account_persistence.md)), performance guardrails ([Performance & Accessibility Standards](./performance_accessibility_standards.md)). +- **Downstream impact:** Onboarding wizard, home dashboard, BTC/Macro screens, scenario persistence, analytics, and any marketing surfaces showing live pricing (see [Live Price Onboarding Integration](./live_price_onboarding_integration.md)). + +## Objectives +1. Automate retrieval of the latest BTC/USD daily close from a reputable public API. +2. Persist fetched prices with audit metadata so the system can power dynamic base-year calculations and user defaults. +3. Expose API and event surfaces that keep the web app in sync with the most recent price while handling outages gracefully. +4. Provide operational visibility and failover controls so pricing stays reliable without manual intervention. + +## Scope +- Selection and integration of one or more upstream data providers (primary + optional fallback). +- Backend jobs/services that fetch, validate, and store prices on a daily cadence. +- API endpoints and pub/sub events to distribute updated pricing to front-end clients and other services. +- Administrative tooling and observability for troubleshooting ingestion issues. + +## Non-Goals +- Real-time streaming or intraday minute-by-minute updates (daily close suffices for modeling). +- Implementing full historical backfill (covered in [Dynamic Base-Year Handling](./dynamic_base_year_handling.md)). +- Building user-facing charting dashboards beyond what is required for modeling screens. + +## Functional Requirements +1. **Provider integration** + - Support at least one primary provider (e.g., CoinGecko `/coins/bitcoin/history` or Coinbase `/prices/BTC-USD/spot`). + - Optional secondary provider configured as fallback with health checks. + - Allow provider configuration via environment variables for different deployments. +2. **Fetch cadence** + - Schedule automated job to run shortly after UTC midnight (configurable window) to capture the previous day's close. + - Provide manual trigger (CLI or admin endpoint) to re-fetch for a given date when corrections are required. + - Enforce exponential backoff and retry limits; surface failures to alerting stack. +3. **Data validation** + - Normalize provider response to `{ date, closeUsd, source, fetchedAt }` in UTC. + - Reject values that deviate more than configurable percentage (e.g., ±10%) from the last stored value unless explicitly overridden. + - Ensure monotonic date progression; prevent duplicate entries without audit trail. +4. **Persistence** + - Insert records into `pricing_daily` with conflict handling (updates should create audit entries in `pricing_daily_audit`). + - Track `provider_latency_ms`, `status`, and `checksum` fields to aid troubleshooting. + - Maintain service metadata on last successful ingestion timestamp and provider used. +5. **Distribution** + - Publish ingestion results to message bus (e.g., `pricing.latest` topic) so dependent services (calculation engine, cache warmers) can react. + - Expose REST endpoint `GET /api/pricing/latest` returning `{ latestDate, latestPrice, source, fetchedAt }` with caching headers. + - Implement webhook/event emitter for front end to optionally subscribe via SSE/WebSocket for near-real-time updates. +6. **Resilience & fallbacks** + - If primary provider fails consecutively (configurable threshold), automatically switch to fallback and raise alert. + - If all providers fail, reuse last successful price but mark status as `STALE`; notify ops channel. + - Provide runbooks for manual data entry/backfill. +7. **Security & compliance** + - Store provider API keys/credentials securely (secrets manager or environment variables with restricted access). + - Rate-limit public endpoints and ensure admin controls require authentication with appropriate roles. + - Log all ingestion attempts with provider responses (sanitized) for audit. + +## Architecture & Components +- **Worker service (`pricing-ingestor`)** responsible for scheduled pulls, validation, persistence, and event publication. +- **Shared pricing module** (extends `@bitcoin24/core/pricing`) exposing helpers like `fetchLatestPriceFromProvider`, `validatePrice`, `savePriceRecord`. +- **API gateway** endpoints layered on existing backend (e.g., Next.js API routes or FastAPI) delegating to pricing module. +- **Background scheduler** using hosted cron (e.g., GitHub Actions, AWS EventBridge, Supabase cron) triggering ingestion job. +- **Monitoring stack** leveraging existing observability tooling (Sentry, Datadog) to emit metrics and alerts. + +## Implementation Steps +1. **Provider evaluation & abstraction** + - Benchmark response quality, rate limits, and latency for candidate APIs. + - Create provider interface `PricingProvider` with methods `getDailyClose(date)` and `getLatest()`; implement adapters per provider. +2. **Ingestion worker** + - Scaffold worker job with lifecycle hooks: `prepare` → `fetch` → `validate` → `persist` → `publish`. + - Support CLI `yarn pricing:ingest --date=YYYY-MM-DD` for manual runs. + - Integrate with scheduler via command or HTTP webhook. +3. **Validation & persistence layer** + - Reuse tables defined in task #12; add new columns `provider`, `status`, `latency_ms` as needed. + - Implement optimistic locking/transactions to guard against concurrent writes. + - Write unit tests covering acceptance/rejection scenarios. +4. **Distribution endpoints & events** + - Add REST `GET /api/pricing/latest` and `POST /api/pricing/refresh` (authenticated) endpoints. + - Emit event payloads conforming to `PricingUpdatedEvent` schema; document message contract. + - Update shared front-end hooks to listen for events and refresh local caches. +5. **Observability & alerts** + - Record metrics: success/failure counts, provider latency, drift from previous close, stale duration. + - Configure alerts for consecutive failures, stale data thresholds, and validation rejections. + - Document runbooks and escalation paths in ops wiki (link TBD). +6. **QA & rollout** + - Stage environment dry run with mock providers to validate scheduler & retries. + - Backfill missing days via manual CLI to ensure workflow handles historical corrections. + - Launch to production with heightened monitoring during first week; review logs daily. + +## Front-End & UX Touchpoints +- Onboarding wizard and home dashboard display "Latest BTC close: ${price} (as of {date})" using `/api/pricing/latest`. +- Provide inline status chip (Fresh/ Stale) with tooltip copy explaining data recency. +- Trigger gentle toast when a newer price becomes available while user is active. +- Follow accessibility guidance: convey status via icons/labels, not color alone. + +## Performance Considerations +- Cache `GET /api/pricing/latest` at edge for short TTL (e.g., 60s) while ensuring updates propagate quickly after ingestion. +- Use connection pooling and batch writes when backfilling large date ranges. +- Keep worker dependencies lightweight to reduce cold-start time on serverless platforms. + +## Security Considerations +- Restrict admin endpoints to privileged roles; log request metadata. +- Apply request signing or IP allowlists if providers support callbacks/webhooks. +- Sanitize provider responses before logging to avoid inadvertently storing PII. + +## Testing Strategy +- **Unit tests:** provider adapters (happy path & error handling), validation rules, persistence logic. +- **Integration tests:** end-to-end ingestion run against sandbox provider, ensuring DB records and events emit correctly. +- **Contract tests:** verify `/api/pricing/latest` response schema consumed by front end. +- **Load tests:** simulate backfill of several months to ensure job handles rate limits and DB constraints. + +## Delivery Milestones +1. Provider abstraction & sandbox integration completed. +2. Scheduled ingestion job running in staging with monitoring. +3. API endpoints and event distribution wired to front end. +4. Production launch with alerting and runbooks handed off to operations. + +## Open Questions +- Which provider SLAs satisfy business requirements, and do we need a paid tier for reliability? +- Should we store OHLC data for future analytics or stick to daily close only for now? +- Do marketing pages require unauthenticated access to latest price, influencing caching strategy? + diff --git a/docs/flow_specific_ux_interactions.md b/docs/flow_specific_ux_interactions.md new file mode 100644 index 0000000..49a1f43 --- /dev/null +++ b/docs/flow_specific_ux_interactions.md @@ -0,0 +1,78 @@ +# Flow-Specific UX Interactions & Navigation Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +This blueprint informs tasks **3**, **10**, **12**, **13**, **14**, and **22** from the [Development Plan](./development_plan.md), ensuring the onboarding, guided model progression, dynamic pricing interactions, live price decisions, and validation/help strategies deliver a coherent user journey. + +This guide translates the onboarding, home, and modeling flows into detailed UX behaviors that align with the Bitcoin Model web app's design language. It supplements the overarching design system and theming/motion frameworks, and pairs with the [Route Guarding & Navigation](./route_guarding_navigation.md) blueprint for implementation specifics. For Step 1 UI patterns see the [BTC Model Screen Blueprint](./btc_model_screen.md); for the price-selection journey reference the dedicated [Live Price Onboarding Integration](./live_price_onboarding_integration.md); for macro-screen execution details consult the [Macro Model Screen Blueprint](./macro_model_screen.md); and for Step 3 behavior align with the [Individual Micro Model Screen Blueprint](./individual_micro_model_screen.md). + +Persistence interactions described here should adhere to the save/load patterns codified in the [Scenario Persistence Controls](./scenario_persistence_controls.md) plan and stay synchronized with the base-year behavior defined in the [Dynamic Base-Year Handling](./dynamic_base_year_handling.md) specification and the live pricing pipeline outlined in the [External BTC Price Ingestion](./external_btc_price_ingestion.md) blueprint. + +## 1. Cover Screen & Entry CTA +- **Hero Treatment:** Full-bleed dark gradient with animated particle backdrop and centered copy highlighting the product promise. +- **Primary CTA:** "Get Started" button using accent gradient, spring hover lift, and subtle glow to draw focus. +- **Secondary Options:** Inline text link for "View docs" and footer links for legal/about content, keeping the entry uncluttered. +- **Performance:** Preload onboarding route assets so the modal/wizard opens instantly. + +## 2. Onboarding Wizard +- **Structure:** Three-panel flow (Welcome → Account → Confirmation) displayed as full-height modal with blurred backdrop. +- **Progress Indicator:** Stepper at top with animated progress bar and labels ("Welcome", "Create account", "You're in"). +- **Inputs:** Username, email, password fields with inline validation using Zod; show password strength meter and requirements tooltip. +- **Live BTC Price Selection:** Present latest price fetched from backend with timestamp. Offer radio choices: "Use live price", "Pick historical snapshot", "Enter custom". +- **Transitions:** Framer Motion slide-in/out with `spring` easing; respect `prefers-reduced-motion` to swap for fade transitions. +- **Error Handling:** Inline alerts styled with accent warning color; allow users to retry without resetting prior inputs. +- **Exit Paths:** "Back to cover" text button and keyboard `Esc` support; preserve partially entered data in state. + +## 3. Post-Onboarding Redirect & Toasts +- **Success Toast:** Floating confirmation showing "Account created" with option to view saved scenarios. +- **Auto-Save:** Immediately persist selected BTC price preference and default scenario seed tied to the new user. +- **Routing:** Redirect to `/home` with optimistic navigation; fetch user data in parallel, showing skeleton cards until data resolves. + +## 4. Home Dashboard +- **Hero Card:** Personalized greeting, quick summary of last scenario touched, and "Resume" button. +- **Model Menu:** Responsive grid of cards categorized by flow (BTC Core, Macro Engine, Individual, Corporate, Institution, Nations). Each card contains brief description, completion status chip, and CTA. +- **Quick Actions:** Top-right action bar with "Start guided flow", "Create blank scenario", and "Import from Excel" (future enhancement placeholder). +- **Saved Scenarios List:** Collapsible panel with table layout (name, model type, last updated, actions). Provide search/filter chips. +- **Guided Flow Banner:** Highlight recommended sequence (Step 1 BTC → Step 2 Macro → Step 3 Choose model) with progress pills. Clicking steps deep-links to relevant screen, carrying state. + +## 5. Global Navigation & Layout Shell +- **App Bar:** Sticky top bar featuring logo, breadcrumb trail, notifications, and user avatar menu (profile, settings, sign out). +- **Left Rail:** Context-aware nav chips for BTC, Macro, and current model; highlight active section with glowing border. +- **Responsive Behavior:** Collapse left rail into floating bottom nav on tablet/mobile with icons and text labels. +- **Loading States:** Use skeleton cards for hero summaries and shimmer placeholders for tables while data loads. + +## 6. Guided Model Flow Interactions +- **Step Header:** Sticky header on BTC, Macro, and model screens showing "Step X of 3", page title, and CTA row (Back, Next, Save). +- **Auto-Save Feedback:** Display subtle "Saved" checkmark when inputs persist; escalate to warning banner if persistence fails. +- **Scenario Presets:** Expose toggle buttons for Bear/Base/Bull (BTC) or strategy presets (micro models) with animated selection states. +- **Inline Education:** Tooltip icons next to key fields linking to knowledge base entries; show microcopy on hover/focus. +- **Collapsible Calculations:** Advanced sections default collapsed with disclosure triangles; animate height transitions for smooth reveal. +- **Chart Interactions:** Hover tooltips with formatted currency/BTC units, ability to pin comparisons, and toggle series visibility. + +## 7. Live BTC Price & Historical Context UI +- **Context Strip:** Top of BTC/Macro screens includes banner displaying "Starting from [Price] as of [Date]" with edit button. +- **Historical Picker Modal:** Calendar selector or dropdown for historical anchor year; disable dates newer than latest stored price. +- **Status Alerts:** If live price fetch fails, surface non-intrusive warning with retry option and fallback value. + +## 8. Scenario Management UX +- **Scenario Bar:** On each model screen, show scenario name with dropdown for quick switch, save, duplicate, rename, and delete actions. +- **Version History:** Provide timeline modal listing previous saves with timestamps; allow revert action (future enhancement flag). +- **Confirmation Patterns:** Use bottom-right toast confirmations for save/delete and modal confirmation for destructive actions. + +## 9. Accessibility & Keyboard Support +- Ensure onboarding wizard, scenario menus, and collapsible panels are keyboard accessible with logical tab order and visible focus rings. +- Provide skip links to jump to main content or table sections. +- Offer hotkeys for "Save" (Cmd/Ctrl+S), "Next" (Cmd/Ctrl+→), and "Back" (Cmd/Ctrl+←) with tooltip discoverability. + +## 10. Performance Considerations +- Prefetch next-step route bundles when user is midway through current step. +- Memoize chart datasets and use virtualization for long tables to keep interactions responsive. +- Batch state updates via Zustand selectors and React Transition APIs to avoid jank during intense input changes. + +## 11. Open Questions & Follow-Ups +- Finalize copy for helper tooltips and empty states. +- Determine whether to integrate an interactive tutorial overlay for first-time users. +- Align analytics events with UX milestones (onboarding completion, scenario save, price selection). + +Refer back to the [Design System & Tech Stack](./design_system.md), [Shared App Foundation Blueprint](./shared_app_foundation.md), [Authentication & Account Persistence Blueprint](./authentication_account_persistence.md), and [Theming & Motion Framework](./theming_motion_framework.md) documents, along with the dedicated [Onboarding Wizard Implementation Blueprint](./onboarding_wizard.md), [User Home Page Implementation Blueprint](./user_home_page.md), and [Guided Flow Blueprint](./guided_model_flow.md) for visual, architectural, and implementation guardrails that complement this flow blueprint. diff --git a/docs/guided_model_flow.md b/docs/guided_model_flow.md new file mode 100644 index 0000000..5ca432d --- /dev/null +++ b/docs/guided_model_flow.md @@ -0,0 +1,140 @@ +# Guided BTC → Macro → Model Flow Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #10 – Guided BTC → Macro → model flow. +- **Upstream dependencies:** + - Authenticated access & navigation shell ([Route Guarding & Navigation](./route_guarding_navigation.md)). + - Scenario persistence foundations ([Authentication & Account Persistence](./authentication_account_persistence.md)) and save/load UX ([Scenario Persistence Controls](./scenario_persistence_controls.md)). + - Onboarding completion state & user home selection ([Onboarding Wizard Blueprint](./onboarding_wizard.md), [User Home Page Blueprint](./user_home_page.md)). +- **Downstream consumers:** BTC, Macro, and all micro/nation modeling screens rely on this framework for sequencing, status hand-offs, and auto-save cues; reference the [BTC Model Screen Blueprint](./btc_model_screen.md) for Step 1 UI specifics, the [Macro Model Screen Blueprint](./macro_model_screen.md) for detailed Step 2 implementation requirements, and the [Individual Micro Model Screen Blueprint](./individual_micro_model_screen.md) for Step 3 execution guidance. + +## 1. Goals & Experience Principles +1. **Clarity** – make it obvious what step the user is on, what is required next, and how prior assumptions carry forward. +2. **Continuity** – persist state seamlessly across navigation and refreshes so users never lose progress. +3. **Control** – let users revisit earlier steps, branch into alternate models, or pause and resume without confusion. +4. **Performance** – transitions should feel instantaneous with optimistic UI and prefetching of upcoming data. +5. **Trust** – surface auto-save confirmations, validation status, and scenario context to reinforce reliability. + +## 2. Scope +- Sequencing logic and UI for progressing from BTC assumptions → Macro assumptions → selected model. +- Persistent stepper/overview bar that tracks completion, warnings, and quick navigation. +- Auto-save + draft handling tied to scenario persistence APIs. +- Contextual guidance (tooltips, help drawers) that explains the relationship between steps. +- Exit/resume flows from the home dashboard and scenario library. + +## 3. Non-Goals +- Detailed form fields within each modeling screen (covered by forthcoming BTC/Macro/model UI specs). +- Scenario comparison visualizations (handled in the specific screen blueprints). +- Implementation of live pricing selection (see [Flow-Specific UX Interactions](./flow_specific_ux_interactions.md) and [Live Price Onboarding Integration](./live_price_onboarding_integration.md)). + +## 4. User Journeys +### 4.1 First-time signed-in user +1. Completes onboarding wizard → lands on Home with “Start modeling” CTA. +2. Chooses a model category (Individual, Corporate, etc.). +3. Guided flow initiates at BTC step with an info panel explaining the sequence. +4. User enters/accepts BTC assumptions → presses “Next”. +5. Macro step preloads with BTC outputs; user adjusts macro levers. +6. Upon “Next”, system precomputes chosen model inputs and routes to the model screen with relevant defaults. +7. Completion banner confirms scenario saved; user can jump to comparisons or return Home. + +### 4.2 Returning user with existing scenario +1. From Home, selects a saved scenario. +2. Stepper highlights all completed steps; unsaved changes indicators reset. +3. User can jump directly into any step; unsaved edits trigger confirmation modals when navigating away. +4. Auto-save occurs on blur or interval; toast confirms success. + +### 4.3 Deep link from shareable route +1. Recipient opens link `/models/individual?scenario=` while authenticated. +2. Flow shell loads with BTC/Macro steps marked as read-only snapshots (unless user duplicates scenario). +3. Duplicate action creates new draft and resets stepper to editable state. + +## 5. Functional Requirements +1. **Stepper UI & State** + - Displays three primary steps plus a dynamic fourth slot for the selected model (Individual, Corporate, Institution, Nation – label adapts). + - Shows status per step: *Not started*, *In progress*, *Completed*, *Attention needed* (validation issues). + - Includes progress bar and estimated time per step (pulled from analytics averages when available). +2. **Navigation Controls** + - “Next” and “Back” buttons pinned to bottom right/left with keyboard shortcuts (⌘/Ctrl + →/←). + - “Save & exit” button opens modal summarizing last saved time and links back to Home. + - If validation fails, “Next” scroll-locks to first invalid field and displays inline messaging. +3. **Auto-Save & Draft Handling** + - Draft state stored per scenario with versioning; includes `updated_at`, `updated_by`, diff summary. + - Autosave triggers on debounce (1.5s after change) and on navigation; display toast + step indicator checkmark. + - Conflicts handled with last-writer-wins plus warning banner if server version is newer. +4. **Data Prefetching & Hydration** + - On entering BTC step, prefetch Macro schema; on Macro, prefetch selected model data definitions to reduce wait time. + - Use React Query/SWR with background refresh to keep derived outputs warm. +5. **Validation & Warnings** + - Global validation bus collects issues from each step and surfaces summary in stepper badges. + - Warnings (non-blocking) displayed as amber icons; blocking errors as red. + - CTA disabled only for critical errors; warnings allow progression but persist in overview until acknowledged. +6. **Scenario Context Bar** + - Sticky summary at top: Scenario name, last saved timestamp, active user, environment badge. + - Includes quick actions: rename, duplicate, switch scenario (opens drawer), download report (future). +7. **Responsive Behavior** + - On <1024px width, stepper collapses into top progress pills with horizontal scroll; bottom nav transforms into floating fab cluster. + - Maintain accessible focus order and skip links for keyboard users. + +## 6. Architecture & State Management +- Implement dedicated `guidedFlow` slice in global store (Zustand/Redux Toolkit) storing `currentStep`, `completion`, `validation`, and `scenarioId`. +- Persist state to local storage keyed by user + scenario for offline resilience. +- Expose selectors/hooks for screens to publish validation status and register unsaved changes. +- Use central `FlowCoordinator` component to wrap BTC/Macro/model routes; handles transitions, analytics events, and error boundaries. +- Coordinate with `ScenarioService` for CRUD operations and `ComputationService` for recalculations upon step transitions. + +## 7. UX & Content Requirements +- Provide inline explainer text at top of each step referencing the relationship (e.g., “Macro assumptions translate BTC adoption into global asset flows”). +- Display “Need help?” button linking to contextual documentation or tooltips referencing workbook cells. +- Offer “View calculations” toggle to reveal derived tables without leaving step. +- Support dark theme styling consistent with design system (glassmorphism cards, neon highlight for active step). + +## 8. Accessibility +- Stepper should be navigable via keyboard (tab/arrow keys) with `aria-current="step"` semantics. +- Announce step changes via ARIA live regions. +- Ensure color contrast for step status badges meets WCAG AA; provide text + icon for status. +- Provide focus outlines and skip-to-content link above scenario context bar. + +## 9. Performance Considerations +- Prefetch next-step bundles using route-based code splitting; ensure <100ms transition on warm path. +- Use Suspense fallback skeletons matching card layouts to avoid layout shift. +- Cache heavy computation results per step to avoid redundant recomputations on back navigation. +- Instrument Web Vitals and custom metrics (step transition duration, autosave latency) via analytics SDK. + +## 10. Security & Resilience +- Respect authorization checks: only scenario owner or collaborators can edit; others receive read-only mode with duplication option. +- Handle network failures with persistent banners and retry controls; autosave should queue offline edits. +- Record audit log entries for step transitions, auto-saves, and validation overrides. +- Ensure unsaved changes modal warns users before closing tab/window (navigator `beforeunload`). + +## 11. Testing Strategy +- **Unit tests:** reducers/selectors for `guidedFlow` store, validation aggregator, autosave scheduler. +- **Integration tests:** Cypress/Playwright flows covering first-time setup, returning user resume, validation block, conflict resolution. +- **Contract tests:** ensure API payloads for autosave and scenario transitions align with backend expectations. +- **Accessibility tests:** automated Axe checks for stepper semantics, manual screen reader run-through. + +## 12. Analytics & Telemetry +- Track events: `flow_step_viewed`, `flow_step_completed`, `flow_validation_error`, `flow_autosave_success`, `flow_resume_clicked`. +- Capture timing metrics for completion of each step and total flow to inform UX tuning. +- Record drop-off points and surface them in analytics dashboards for prioritization. + +## 13. Milestones & Deliverables +1. **M1 – Infrastructure setup**: Implement `FlowCoordinator`, global store slice, skeleton UI with placeholder steps. +2. **M2 – Autosave + validation plumbing**: Hook into scenario service, implement validation bus, add optimistic toasts. +3. **M3 – UX polish & accessibility**: Finalize responsive stepper, keyboard support, ARIA messaging, analytics instrumentation. +4. **M4 – Beta hardening**: Load testing for autosave endpoints, conflict resolution QA, telemetry dashboards live. + +## 14. Open Questions +- Do we enable branching into multiple model types within the same scenario (e.g., compare individual vs. corporate) or enforce one model per scenario? +- Should we support collaborative editing (multiple users in same scenario) in MVP, or treat as future enhancement? +- What is the retention policy for autosave history and draft versions? + +## 15. Related Documents +- [Design System & Tech Stack](./design_system.md) +- [Theming & Motion Framework](./theming_motion_framework.md) +- [Flow-Specific UX Interactions](./flow_specific_ux_interactions.md) +- [Route Guarding & Navigation](./route_guarding_navigation.md) +- [Authentication & Account Persistence](./authentication_account_persistence.md) +- [User Home Page Blueprint](./user_home_page.md) + diff --git a/docs/individual_micro_model_screen.md b/docs/individual_micro_model_screen.md new file mode 100644 index 0000000..091f3d5 --- /dev/null +++ b/docs/individual_micro_model_screen.md @@ -0,0 +1,98 @@ +# Bitcoin24 Web App – Individual Micro Model Screen Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #17 – Individual micro model screen implementation. +- **Upstream dependencies:** Shared BTC & macro outputs ([BTC Model Screen Blueprint](./btc_model_screen.md), [Macro Model Screen Blueprint](./macro_model_screen.md)), scenario persistence services ([Scenario Persistence Controls](./scenario_persistence_controls.md)), authentication/onboarding ([Authentication & Account Persistence](./authentication_account_persistence.md), [Onboarding Wizard Blueprint](./onboarding_wizard.md)), guided navigation ([Guided Flow Blueprint](./guided_model_flow.md), [Route Guarding & Navigation](./route_guarding_navigation.md)), pricing infrastructure ([Dynamic Base-Year Handling](./dynamic_base_year_handling.md), [External BTC Price Ingestion](./external_btc_price_ingestion.md)), and live price onboarding ([Live Price Onboarding Integration](./live_price_onboarding_integration.md)). +- **Downstream impact:** Corporate, institution, and nation-state models reuse table/chart primitives and scenario persistence patterns established here; shared UI components (task 21) should draw from the abstractions proven in this screen. + +## 1. Objectives +1. Deliver a modern, high-fidelity interface that mirrors the Excel "Individual" sheet while embracing the Bitcoin24 design system. +2. Enable users to explore preset strategies (Normie → Triple Maxi), customize assumptions, and immediately view 21-year projections. +3. Provide rich comparisons (tables + charts) for 2045 outcomes and annual trajectories, integrated with auto-save and guided navigation cues. +4. Maintain performance and accessibility targets—virtualized data grids, responsive layout, keyboard navigation, and descriptive tooltips. + +## 2. Scope +- Strategy selector with preset pills/cards (Normie, BTC 10%, BTC Maxi, Double Maxi, Triple Maxi) plus saved custom scenarios. +- Assumptions workspace including income, expenses, asset allocation, mortgage leverage, savings rate, and BTC conversion percentages. +- Annual forecast tables covering income statement, BTC purchases (surplus vs. debt), balance sheet evolution, and treasury holdings from start year → 2045. +- Scenario comparison grid summarizing 2045 metrics (net assets, BTC holdings, CAGR, LTV, debt) across strategies. +- Chart suite: (1) 2045 net assets bar chart, (2) 2045 BTC count bar chart, (3) 2024/Start-year → 2045 combo chart (net assets vs. BTC count). +- Action row with "Back: Macro", "Next: Save & Exit" (or "Next: Home"), scenario save/duplicate/delete, and CSV export. + +## 3. Non-Goals +- Recreating workbook macro logic (handled in shared calculation services). +- Implementing corporate/institution/nation fiscal mechanics (covered in later tasks). +- Building analytics dashboards beyond the specified charts. + +## 4. Data & State Dependencies +- Consume normalized assumption + result payload from calculation engine (`/models/individual`) keyed by strategy and scenario ID. +- Subscribe to shared pricing context (start year, live price timestamp) for contextual banners. +- Persist user edits via scenario persistence API (auto-save on blur + manual save CTA). +- Utilize Zustand/React Query slices defined in shared foundation for consistent cache updates and optimistic UI. + +## 5. User Experience & Layout +- **Global shell:** Authenticated layout with breadcrumb `Home / Guided Flow / Individual Model`, stepper badge “Step 3 of 3”. +- **Hero KPI band:** Four KPI cards summarizing Current Year Net Assets, 2045 Net Assets, 2045 BTC Holdings, and CAGR, with status chips indicating delta vs. base strategy. +- **Strategy rail:** Horizontal pill selector featuring preset names, short descriptions, and preview stats; include "Custom" slot tied to saved scenarios. +- **Assumptions panel:** + - Split into collapsible sections (Income & Savings, Assets & Liabilities, BTC Allocation, Mortgage & Debt, Taxes & Inflation). + - Inline validation, helper tooltips referencing glossary, and quick-reset buttons to revert to preset defaults. + - Support inline editing with formatted currency/percentage inputs, keyboard shortcuts, and accessible labels. +- **Results workspace:** + - Sticky tab set toggling between Annual Projections and 2045 Snapshot. + - Annual Projections tab hosts a virtualized table with columns grouped per section (Income, Expenses, Savings, BTC Purchases, Debt, Net Worth). + - Provide row grouping for milestone years (initial, mid-point, 2045) and inline sparklines for quick trend scanning. + - 2045 Snapshot tab displays comparison table plus KPI chips summarizing leverage ratio, debt service coverage, BTC % of net worth. +- **Charts row:** + - Responsive grid of three chart cards with shared legend and color palette; support toggling logarithmic scale for BTC holdings. + - Tooltips display formatted currency/BTC units and highlight the selected strategy vs. alternatives. +- **Insight drawer:** Optional right-rail containing textual commentary, assumption notes, and link to documentation. +- **Action footer:** Persistent bar with navigation (Back, Next/Home), Save controls, scenario menu, export button, and validation summary (e.g., warnings). + +## 6. Interaction & Behavior Requirements +1. **Preset selection:** Switching presets should trigger optimistic loading state (<200ms), apply preset defaults, and log analytics event. Unsaved changes prompt confirmation before switching. +2. **Scenario save/load:** Provide dropdown of saved variants with rename/delete actions (per scenario persistence guide). Auto-save after debounce; show toast confirmations. +3. **Validation:** Enforce min/max on percentages (0–100%), non-negative asset values, mortgage caps (e.g., ≤90% LTV), with inline error states and aggregated summary in footer. +4. **Historical context:** Display banner "Projections start from [Start Year] using price $X as of [Date]" with "Adjust" link to open shared price picker. +5. **Guided flow integration:** Completing required fields enables "Next" button; if validation issues remain, show tooltip listing blockers. Navigating back to Macro retains unsaved edits via local draft store. +6. **Chart/table sync:** Hovering rows highlights corresponding chart series; selecting a chart bar highlights row. Provide keyboard navigation for charts (focusable data points). +7. **Accessibility:** Provide ARIA labels for scenario controls, ensure focus order flows logically, and support screen-reader descriptions for KPI deltas. + +## 7. Performance Considerations +- Virtualize tables (e.g., React Virtualized) to maintain 60fps when scrolling 21-year projections. +- Memoize derived chart datasets using selectors; reuse color tokens from theming framework. +- Prefetch macro + BTC data when user enters screen to avoid blocking render; show skeleton loaders while awaiting responses. +- Batch scenario persistence requests and throttle analytics events to avoid network thrash during rapid edits. + +## 8. Security & Privacy +- Respect auth guard requirements; redirect unauthenticated users to onboarding. +- Ensure sensitive scenario data is fetched via HTTPS with auth token/cookie; mask personally identifiable details in logs. +- Honor role-based access (future multi-role support) by checking scope before rendering advanced controls. + +## 9. Testing Strategy +- **Unit tests:** Validate state reducers/selectors, input validation helpers, and chart data transformers using Vitest. +- **Component tests:** Use React Testing Library to ensure preset switching, validation states, and auto-save interactions behave correctly. +- **Integration tests:** Playwright flows for "load preset → modify → auto-save → navigate back → return with persisted values" and CSV export. +- **Visual regression:** Capture Storybook snapshots for key states (each preset, validation error, loading skeletons). +- **Performance checks:** Lighthouse CI focusing on Time to Interactive & interactivity metrics on this route; monitor React Profiler for hydration cost. + +## 10. Analytics & Telemetry +- Track events: preset_selected, assumption_edited, scenario_saved, validation_error, chart_toggle, csv_exported. +- Capture performance metrics (time_to_first_render, table_scroll_latency) for monitoring. +- Log pricing context selections to correlate with outcome changes (respecting privacy guidelines). + +## 11. Rollout Plan +1. Build foundational components (assumption sections, virtualized table, chart cards) in Storybook; verify design adherence. +2. Integrate API hooks and state slices; implement presets and auto-save flows behind feature flag. +3. Conduct internal QA with seeded sample scenarios; collect feedback from designers/product on accuracy vs. Excel sheet. +4. Enable route in staging; monitor analytics and performance dashboards. +5. Roll out to production with progressive exposure; gather user feedback for adjustments before cloning patterns for other micro/nation screens. + +## 12. Open Questions +- Should we allow users to customize preset names or create additional custom strategies beyond the five defaults? +- Do we need per-year annotations (e.g., halving years) surfaced inline within the table or chart tooltips? +- What thresholds determine warning vs. critical status chips (e.g., debt-to-income) for 2045 snapshot KPIs? + +Refer to the [Design System & Tech Stack](./design_system.md), [Theming & Motion Framework](./theming_motion_framework.md), [Flow-Specific UX Interactions](./flow_specific_ux_interactions.md), [Shared App Foundation Blueprint](./shared_app_foundation.md), and [Performance & Accessibility Standards](./performance_accessibility_standards.md) for overarching design, technical, and quality guardrails that apply to this screen. diff --git a/docs/institution_micro_model_screen.md b/docs/institution_micro_model_screen.md new file mode 100644 index 0000000..c07cc45 --- /dev/null +++ b/docs/institution_micro_model_screen.md @@ -0,0 +1,90 @@ +# Bitcoin24 Web App – Institution Micro Model Screen Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #19 – Institution micro model screen implementation. +- **Upstream dependencies:** Macro and BTC projections ([Macro Model Screen Blueprint](./macro_model_screen.md), [BTC Model Screen Blueprint](./btc_model_screen.md)), corporate treasury patterns ([Corporate Micro Model Screen Blueprint](./corporate_micro_model_screen.md)), shared calculation services ([Shared App Foundation Blueprint](./shared_app_foundation.md)), authentication/persistence ([Authentication & Account Persistence](./authentication_account_persistence.md), [Scenario Persistence Controls](./scenario_persistence_controls.md)), navigation/onboarding ([Onboarding Wizard Blueprint](./onboarding_wizard.md), [Guided Flow Blueprint](./guided_model_flow.md), [Route Guarding & Navigation](./route_guarding_navigation.md)), pricing infrastructure ([Dynamic Base-Year Handling](./dynamic_base_year_handling.md), [External BTC Price Ingestion](./external_btc_price_ingestion.md)), and live price UX ([Live Price Onboarding Integration](./live_price_onboarding_integration.md)). +- **Downstream impact:** Nation-state model blueprint will reuse allocation, debt, and comparison primitives defined here; shared UI component work (task 21) should extract mature table/chart abstractions validated on this screen. + +## 1. Objectives +1. Deliver a modern institutional treasury modeling workspace that mirrors the Excel "Institution" sheet while embracing the Bitcoin24 design language. +2. Allow asset managers to toggle between strategy presets, customize portfolio conversion assumptions, and inspect immediate recalculation feedback. +3. Surface 21-year projections, 2045 comparison tables, and chart visualizations that clarify BTC accumulation, asset mix shifts, and leverage usage. +4. Maintain responsive, accessible, and performant interactions for dense financial tables across desktop and tablet breakpoints. + +## 2. Scope +- Strategy selector with preset cards (e.g., Legacy Portfolio, Conservative Allocation, Balanced Allocation, BTC Maxi, BTC + Debt) plus slots for saved custom scenarios. +- Assumption workspace covering starting portfolio mix (cash, bonds, equities, alternatives, BTC), conversion percentages over time, debt-to-buy-BTC toggles, leverage caps, growth/return expectations, and operating expense ratios. +- Annual results tables for assets under management, BTC purchased via conversion/debt, debt schedules, treasury composition, and key ratios (BTC % of AUM, leverage, coverage). +- 2045 comparison grid summarizing AUM, BTC holdings, BTC % allocation, leverage, and CAGR for all strategies (presets + saved scenarios). +- Chart suite: (1) 21-year AUM vs. BTC holdings combo chart, (2) 2045 AUM comparison bar chart, (3) 2045 BTC holdings comparison bar chart mirroring workbook visuals. +- Guided navigation footer with Back/Next actions, scenario persistence controls, CSV export, and validation summary alerts. + +## 3. Non-Goals +- Re-implementing individual/corporate/nation-specific fiscal mechanics beyond shared abstractions. +- Building multi-manager collaboration workflows (out-of-scope for initial release). +- Delivering bespoke risk analytics dashboards beyond the prescribed charts and tables. + +## 4. Data & State Dependencies +- Consume normalized institution model payload from calculation API (`/models/institution`) keyed by strategy and scenario ID. +- Ingest macro context (start year, BTC price path, ARR) for consistent banners and KPI calculations. +- Persist edits through scenario persistence layer with optimistic updates, conflict detection, and audit metadata. +- Subscribe to guided flow state to determine stepper progress, validation gating, and navigation transitions. + +## 5. User Experience & Layout +- **Global shell:** Authenticated layout with breadcrumb `Home / Guided Flow / Institution Model`, step badge showing "Step 3 of 3" when accessed via guided sequence. +- **Hero KPI strip:** Cards summarizing Current Year AUM, 2045 AUM, 2045 BTC Holdings, BTC % Allocation, and Portfolio CAGR with delta chips vs. base strategy. +- **Strategy rail:** Horizontal pills/cards showing preset title, short description, and quick metrics; include "Custom" slot tied to saved scenarios. +- **Assumptions workspace:** + - Collapsible sections: Portfolio Composition, Conversion Programs, Debt Strategy, Return & Growth Assumptions, Operating Expenses & Fees, Governance & Constraints. + - Inline tooltips linking to glossary; quick reset to preset defaults; numeric inputs with currency/percentage formatting and accessible labels. + - Conditional reveals for debt issuance schedules, repayment tenor, and rate inputs when leverage toggles are enabled. +- **Results workspace:** + - Tabbed interface: Annual Projections, 2045 Snapshot, Sensitivity (placeholder for future what-if analysis). + - Annual Projections table with grouped columns (Assets, BTC Purchases, Debt, Coverage Ratios). Support column pinning, inline sparklines, and row grouping for milestone years. + - 2045 Snapshot table comparing presets/saved scenarios with highlight state for active scenario and ability to toggle metrics (AUM, BTC count, BTC %). +- **Charts row:** Responsive grid of three chart cards with shared legend, dark-mode friendly palettes, and download/export actions. +- **Insight drawer:** Optional right rail summarizing key takeaways (e.g., debt utilization, allocation shift) with contextual links to documentation. +- **Action footer:** Persistent controls for Back (Macro), Next (Home/Completion), Save, Save As, Duplicate, Delete, Export CSV, plus validation summary and unsaved change indicator. + +## 6. Interaction & Behavior Requirements +1. **Preset switching:** Provide sub-200ms optimistic state updates; confirmation modal appears if unsaved edits exist. Log analytics events for selection. +2. **Scenario management:** Integrate saved scenario dropdown with rename/delete actions; auto-save after debounce (≈1.5s) and surface toast confirmations. +3. **Validation:** Enforce percentage totals (portfolio mix sums to 100%), leverage caps, non-negative cash balances, and debt coverage thresholds. Display inline errors and aggregate summary in footer. +4. **Conversion scheduling:** Allow users to stage conversion programs (e.g., 20% over 4 years); timeline editor should recalculate annual BTC purchases instantly. +5. **Debt toggles:** Enabling debt-to-buy-BTC reveals additional inputs and recalculates tables/charts in real time; disabling should prompt to confirm removal of associated assumptions. +6. **Guided flow integration:** When required inputs are satisfied, Next button activates; validation tooltip enumerates blockers. Navigating back to Macro retains draft edits. +7. **Historical pricing context:** Banner indicates start year and live price used, with "Adjust" link to shared price selector component. +8. **Accessibility:** Ensure keyboard navigation across strategy rail, assumption inputs, tables, and charts; provide ARIA descriptions for KPI deltas and chart tooltips. + +## 7. Performance Considerations +- Virtualize annual tables (React Virtualized/React Window) to maintain 60fps on dense datasets. +- Memoize derived datasets and reuse selectors to avoid redundant recalculations on input changes. +- Prefetch institution model data when user completes Macro step; use React Query for caching and background refresh. +- Lazy-load heavy chart libraries when container becomes visible; display skeleton loaders during async fetches. +- Batch persistence writes and throttle analytics events to reduce network chatter during rapid edits. + +## 8. Security & Compliance +- Enforce authenticated access per route guarding blueprint; redirect unauthenticated users to onboarding. +- Protect API payloads over HTTPS with auth tokens/cookies; validate server-side inputs to prevent injection or malformed data. +- Limit telemetry to aggregated metrics (no PII); honor audit log requirements for scenario changes if enterprise accounts are introduced. + +## 9. Testing Strategy +- **Unit tests:** Cover state reducers, validation helpers, conversion scheduling utilities, and chart data transformers via Vitest. +- **Component tests:** React Testing Library scenarios for preset switching, validation messaging, debt toggle workflows, and auto-save. +- **Integration tests:** Playwright flows for guided navigation (Macro → Institution → Save → Return), scenario lifecycle (create/duplicate/delete), and conversion schedule editing. +- **Visual regression:** Storybook snapshots for hero KPIs, assumption panels, tables, and charts across light/dark themes and responsive breakpoints. +- **Performance checks:** Lighthouse CI thresholds (TTI, LCP) plus React profiler to ensure virtualization prevents jank. + +## 10. Analytics & Telemetry +- Track events such as institution_preset_selected, institution_assumption_edited, conversion_schedule_updated, debt_toggle_changed, institution_scenario_saved, validation_error_shown, and chart_exported. +- Emit guided flow milestones (institution_step_entered, institution_step_completed) for funnel tracking. +- Capture non-PII context (strategy, debt enabled, conversion duration) as event properties for cohort analysis. + +## 11. Rollout & Milestones +1. **Design sign-off:** Validate Figma screens and interaction prototypes align with blueprint before engineering kickoff. +2. **Data contract readiness:** Finalize calculation API schema and sample payloads for presets/custom scenarios. +3. **Development sprint:** Implement UI, integrate APIs, wire persistence, and add tests (unit/component/integration). +4. **QA & accessibility:** Run manual QA, Lighthouse/Axe scans, and cross-browser/device checks; address issues prior to release. +5. **Release & observability:** Deploy behind feature flag, monitor analytics and logs, collect feedback, then graduate to general availability. diff --git a/docs/live_price_onboarding_integration.md b/docs/live_price_onboarding_integration.md new file mode 100644 index 0000000..ef429e9 --- /dev/null +++ b/docs/live_price_onboarding_integration.md @@ -0,0 +1,83 @@ +# Live Price Onboarding Integration Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #14 – Live price onboarding integration. +- **Related guides:** [Onboarding Wizard](./onboarding_wizard.md), [External BTC Price Ingestion](./external_btc_price_ingestion.md), [Dynamic Base-Year Handling](./dynamic_base_year_handling.md), [Flow-Specific UX Interactions](./flow_specific_ux_interactions.md), [Scenario Persistence Controls](./scenario_persistence_controls.md). + +This blueprint defines how the onboarding experience, home dashboard, and modeling entry points should surface live BTC pricing, historical anchors, and custom overrides. It builds on the pricing data pipelines and guided flow mechanics established in the complementary documents listed above. + +## Objectives +1. Present today’s BTC price (with timestamp and source) during onboarding and on the home dashboard so users can anchor scenarios with minimal friction. +2. Allow users to accept the live price, select a historical date, or input a custom value—persisting their choice into their default scenario. +3. Ensure all downstream flows (BTC → Macro → model) receive the selected starting price automatically, while still letting users adjust it later with clear audit context. +4. Handle outages or stale data gracefully by falling back to cached values, surfacing status messaging, and prompting users when manual confirmation is required. + +## Scope +- Onboarding wizard price selection step, including live data fetch, historical picker, and custom input validation. +- Home dashboard widgets summarizing current price selection and allowing quick adjustments. +- Shared state and API contracts that propagate the chosen price to guided flow steps and scenario persistence. +- UX copy, analytics, and accessibility considerations specific to live price handling. + +## Non-Goals +- Changing the underlying pricing ingestion jobs or historical data storage (covered by tasks #12 and #13). +- Implementing advanced market visualizations beyond the selection UI (charts remain part of modeling screens). +- Supporting sub-daily price updates; daily close is sufficient for onboarding and scenario seeding. + +## Functional Requirements +1. **Live Price Retrieval** + - Fetch latest price via `/api/pricing/latest` (see [External BTC Price Ingestion](./external_btc_price_ingestion.md)). + - Display status chip (Fresh, Stale, Error) with tooltip describing data recency. + - Show skeleton loader until data resolves; if request fails after retries, display fallback price with warning state. +2. **Price Selection Options** + - Radio options: `Use live price`, `Pick historical snapshot`, `Enter custom price`. + - Historical selection uses date picker limited to available historical dataset returned from `/api/pricing/history` (to be added as part of dynamic base-year implementation). + - Custom entry validates currency formatting, enforces positive value, and respects locale-aware separators. +3. **Persistence & Propagation** + - On confirmation, call `/api/scenarios` with payload `{ startingPriceSource, startingPriceValue, startingPriceDate }` to seed or update the active scenario. + - Store selection in onboarding state (session storage) and global scenario store so the BTC and Macro screens automatically reflect it. + - Display confirmation toast (“Starting price saved”) with undo option that reverts to live price. +4. **Home Dashboard Integration** + - Add “Current Starting Price” card summarizing value, date, source, and last updated timestamp. + - Include quick actions: `Refresh to live`, `Adjust`, `View history`. Adjust opens the same modal used in onboarding for consistency. + - Highlight discrepancies (e.g., live price has moved >X% since saved) with alert banner prompting user to reconsider their anchor. +5. **Guided Flow Hooks** + - BTC and Macro screens display banner referencing selected starting price with “Edit” button linking back to price modal. + - When user edits from within flow, reuse same API/state logic and update breadcrumbs so progress indicator remains accurate. + - Record analytics event (`starting_price.updated`) with metadata: source, delta from previous value, flow location. +6. **Resilience & Offline Handling** + - Cache last successful price selection locally so UI can render even when offline; mark status as “Offline” and disable live refresh until connection returns. + - Provide manual entry path when live data unavailable—pre-fill with last known price but require confirmation checkbox acknowledging stale data. + +## UX & Visual Guidelines +- Use card layout consistent with design system: glassmorphism background, accent gradient highlights, legible typography in dark theme. +- Progress indicator reflects price selection as Step 3 in onboarding (after account creation). +- Tooltips describe pros/cons of each selection option; include info icon linking to FAQ. +- Accessibility: ensure radio buttons, date picker, and numeric input have clear labels, helper text, and error messaging; respect `prefers-reduced-motion`. + +## Analytics & Telemetry +- Track events for viewing price step, selecting each option, confirming selection, encountering live fetch errors, and overriding from downstream screens. +- Include metadata: `source` (`live`, `historical`, `custom`), `priceValue`, `priceDate`, `deltaFromLive` when applicable. +- Log warnings when scenario price remains unchanged for >30 days to prompt follow-up in future iterations. + +## Testing Strategy +- **Unit Tests:** selection reducer, validation schemas, API handlers for price persistence. +- **Component Tests:** render live/historical/custom paths with MSW mocks, verifying UI states and toasts. +- **Integration Tests:** Playwright scenario covering onboarding selection, home dashboard update, and BTC screen banner reflection. +- **Accessibility Tests:** Axe scans for modal and card, manual keyboard navigation across date picker and inputs. + +## Delivery Milestones +1. Implement shared price selection modal component with state management and validation hooks. +2. Wire onboarding wizard to use component, integrating live API calls and persistence. +3. Extend home dashboard with current price card and adjustment actions. +4. Update BTC/Macro screens to consume shared state and surface edit banner. +5. Complete QA (unit/component/e2e/accessibility) and finalize analytics instrumentation. + +## Open Questions +- What threshold should trigger “price drift” alerts (e.g., >5% change since saved)? +- Should historical picker default to the most recent completed year or allow arbitrary dates back to dataset start? +- Do we require email confirmation before allowing custom price entry (for spam mitigation)? +- How should we communicate when live price is unavailable for extended periods—banner vs. modal vs. email notification? + +For implementation details on the surrounding systems, refer to the [Onboarding Wizard](./onboarding_wizard.md), [User Home Page](./user_home_page.md), [Guided Model Flow](./guided_model_flow.md), [Scenario Persistence Controls](./scenario_persistence_controls.md), [Dynamic Base-Year Handling](./dynamic_base_year_handling.md), and [External BTC Price Ingestion](./external_btc_price_ingestion.md) blueprints. diff --git a/docs/macro_model_screen.md b/docs/macro_model_screen.md new file mode 100644 index 0000000..018dd4f --- /dev/null +++ b/docs/macro_model_screen.md @@ -0,0 +1,103 @@ +# Bitcoin24 Web App – Macro Model Screen Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #15 – Macro model screen implementation. +- **Dependencies:** Shared calculation engine ([Shared App Foundation Blueprint](./shared_app_foundation.md)), authentication and scenario storage ([Authentication & Account Persistence](./authentication_account_persistence.md)), pricing infrastructure ([Dynamic Base-Year Handling](./dynamic_base_year_handling.md), [External BTC Price Ingestion](./external_btc_price_ingestion.md)), guided navigation ([Route Guarding & Navigation](./route_guarding_navigation.md), [Guided Flow Blueprint](./guided_model_flow.md)), and live price onboarding ([Live Price Onboarding Integration](./live_price_onboarding_integration.md)). +- **Downstream impact:** BTC screen, micro/nation model screens, and scenario comparisons depend on macro outputs and should consume shared selectors derived here. + +## Objectives +1. Provide an intuitive, high-performance interface for editing macroeconomic assumptions that drive the entire workbook. +2. Surface computed outputs (BTC price path, market capitalization, asset allocation, government program impacts) with modern visualizations and collapsible detail tables. +3. Keep the experience accessible, responsive, and consistent with the design system while integrating auto-save, scenario management, and guided flow cues. + +## Scope +- UI layout for macro assumptions, monetization sliders, conversion programs, and summary KPIs. +- Rendering of yearly BTC trajectory table, asset allocation matrices, and supporting calculations with expand/collapse controls. +- Two primary chart families: price/market-cap time series and asset-share comparisons. +- Integration with shared state so edits trigger recalculation and persistence. +- Skeleton/loading, optimistic updates, and error handling for computation or persistence failures. + +## Non-Goals +- Re-implementing the pricing ingestion service or historical data sync (covered in pricing blueprints). +- Detailing BTC or downstream model screens beyond how they consume macro outputs. +- Designing analytics dashboards; only instrumentation hooks required for UX/performance tracking are in scope. + +## User Experience & Layout +- **Global chrome:** Within the authenticated shell with breadcrumb `Home / Macro Model`. Display guided-step badge “Step 2 of 3” when user is mid-flow (per [Guided Flow Blueprint](./guided_model_flow.md)). +- **Hero summary band:** Sticky top section with key KPIs (Current BTC Price, 2045 BTC Price, 2045 BTC Market Cap, Bitcoin Share of Global Assets) using KPI card component defined in the design system. +- **Assumption grid:** Two-column responsive layout (collapsing to stacked on <1024px) with grouped panels: + - *Scenario selection:* Toggle preset cases (Bear/Base/Bull) sourced from workbook `Macro!C7:E9`. Show pill buttons with preview of ARR/Inflation values. + - *Inflation, innovation, inefficiency inputs:* Numeric sliders with direct input fields, including tooltips referencing workbook rationale. + - *Asset monetization sliders:* Multi-column table with slider inputs for Gold, Equity, Bonds, Real Estate, Fiat, etc. Provide quick reset to preset values. + - *Government programs:* Toggle and percentage inputs for Treasury Conversion, Conversion Program, Debt Monetization, with contextual helper text. +- **Results workspace:** Tabbed or sectioned area containing: + - *BTC price & market cap table:* Yearly table (latest historical year → 2045) with columns for Year, BTC Price, BTC Market Cap, ARR. Provide sticky header, virtualization for long tables, and ability to export CSV. + - *Asset allocation matrix:* Table showing asset classes vs. year or final allocation (depending on workbook structure). Offer collapse to hide intermediate columns. + - *Conversion program details:* Expandable drawers showing debt issuance, government program contributions, ARR breakdown, and any intermediate values (matching Excel supporting rows). +- **Charts:** + - Combo line/bar chart for BTC price and market cap across projection years. + - Stacked bar or donut chart for 2045 asset share comparisons (BTC vs. Gold vs. Equity, etc.). + - Optional area chart for cumulative conversion program contributions if workbook data supports it. +- **Call-to-actions:** Next button leading to selected model screen, Save Scenario, Reset to Preset, Download CSV. + +## Data & State Requirements +- Consume macro assumption defaults, scenario presets, and formula computation functions from shared app foundation modules. +- Maintain local UI state via centralized store (e.g., Zustand/Redux slice) synced with scenario persistence service. +- On change: + 1. Update local state and optimistic UI. + 2. Trigger recalculation via client-side engine or API call (depending on final architecture). + 3. Debounced auto-save (e.g., 1.5s) to scenario endpoint when authenticated. +- Ensure state references a `startYear` from dynamic pricing service; adjust table axis accordingly. +- Provide ability to revert to last saved scenario; show unsaved changes indicator when local state diverges. + +## Validation & Error Handling +- Range validation for percentage inputs (0–100%), ARR bounds, inflation/innovation rate limits, and numeric constraints pulled from workbook guardrails. +- Inline error messages with accessible descriptions; disable Save/Next when validations fail. +- Handle calculation errors by showing inline banner with retry option; log to observability platform per [Performance & Accessibility Standards](./performance_accessibility_standards.md). +- Gracefully degrade charts/tables when data unavailable (show skeletons or fallback text). + +## Accessibility Considerations +- Ensure keyboard navigation through all controls; provide clear focus states consistent with theming guide. +- Use semantic headings for section hierarchy and ARIA roles for tabs, accordions, and charts (including text alternatives). +- Provide high-contrast mode support; verify color ratios meet WCAG AA. +- Offer screen-reader friendly descriptions for KPI cards and chart data (e.g., hidden table summary or “View data” button). + +## Performance Considerations +- Lazy load heavy chart modules; utilize suspense/skeleton states defined in theming/motion guide. +- Virtualize tables for >20 rows; limit re-renders via memoized selectors. +- Batch state updates and throttle recomputation to maintain 60fps interactions. +- Prefetch downstream model screen bundles when user dwells on CTA (guided flow optimization). + +## Analytics & Telemetry +- Track events: scenario preset selection, individual assumption adjustments (bucketed), chart view toggles, collapsible section usage, Next/Back navigation. +- Record auto-save success/fail metrics, calculation duration, and error occurrences. +- Feed data into observability pipeline defined in performance standards for monitoring. + +## Testing Strategy +- **Unit tests:** Cover selector logic, validation rules, reducers/actions, and UI component rendering for key panels. +- **Integration tests:** Use Playwright/Cypress to simulate editing assumptions, verifying recalculated outputs, navigating Next/Back, and ensuring auto-save triggers. +- **Visual regression:** Capture baseline snapshots of KPI cards, tables, and charts with Chromatic/Loki. +- **Accessibility tests:** Axe automated scans plus keyboard-only walkthroughs. +- **Performance tests:** Measure render and update times under realistic input churn using Lighthouse CI with custom scripts. + +## Security & Privacy +- Ensure sensitive scenario data stored securely; guard API calls with auth tokens and CSRF protections. +- Sanitize user inputs server-side before persisting; enforce rate limiting on recalculation endpoints. +- Obfuscate or redact personally identifiable information in telemetry per privacy policy. + +## Rollout Plan +1. Implement skeleton UI with mocked data to validate layout and interactions. +2. Integrate with live calculation APIs and dynamic pricing service; verify parity with Excel outputs. +3. Wire auto-save and scenario persistence, including conflict resolution for multi-tab usage. +4. Conduct UX review against design system; refine animations and responsive behavior. +5. Run full QA suite (unit, integration, accessibility, performance) before promoting to staging. +6. Beta test with internal stakeholders; gather feedback and iterate on usability or performance issues. +7. Launch alongside BTC screen implementation to enable end-to-end guided flow testing. + +## Open Questions +- Final decision on where macro calculations execute (client vs. server) and latency implications? +- Should advanced users access formula audit logs or download raw intermediate CSVs? +- Do we expose version history for macro assumptions beyond scenario snapshots? + diff --git a/docs/nation_state_model_screens.md b/docs/nation_state_model_screens.md new file mode 100644 index 0000000..1247a61 --- /dev/null +++ b/docs/nation_state_model_screens.md @@ -0,0 +1,100 @@ +# Bitcoin24 Web App – Nation-State Model Screens Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #20 – Nation-state model screen implementations (Indebted Nation, Wealthy Nation, United States). +- **Upstream dependencies:** Macro projections and BTC baseline ([Macro Model Screen Blueprint](./macro_model_screen.md), [BTC Model Screen Blueprint](./btc_model_screen.md)), micro-model abstractions ([Individual Micro Model Screen Blueprint](./individual_micro_model_screen.md), [Corporate Micro Model Screen Blueprint](./corporate_micro_model_screen.md), [Institution Micro Model Screen Blueprint](./institution_micro_model_screen.md)), pricing infrastructure ([Dynamic Base-Year Handling](./dynamic_base_year_handling.md), [External BTC Price Ingestion](./external_btc_price_ingestion.md)), live price UX ([Live Price Onboarding Integration](./live_price_onboarding_integration.md)), authentication/navigation ([Authentication & Account Persistence](./authentication_account_persistence.md), [Route Guarding & Navigation](./route_guarding_navigation.md), [Guided Flow Blueprint](./guided_model_flow.md)). +- **Downstream impact:** Shared UI components (task 21) and validation/analytics layer (task 22) will extract and harden patterns surfaced by these screens; financial reporting outputs inform future reporting exports. + +## 1. Objectives +1. Provide high-fidelity modeling workspaces for Indebted Nation, Wealthy Nation, and United States scenarios that respect each sheet’s fiscal assumptions and storytelling. +2. Allow policy makers to toggle treasury conversion levers, program durations, debt issuance strategies, and see immediate recalculation feedback. +3. Surface transparent annual projections, 2045 comparison tables, and chart suites that communicate fiscal balance shifts, BTC accumulation, and reserve composition. +4. Maintain usability for dense data (keyboard accessible tables, responsive layout, performant virtualization) while aligning with the Bitcoin24 visual design system. + +## 2. Scope +- Separate routes for each nation with shared shell components but customized copy, preset values, and contextual banners (e.g., US government publication note). +- Assumption workspaces covering revenues, expenditures, GDP growth, inflation, cost of debt, treasury asset allocations, conversion percentages, program timing, surplus sweep toggles, and debt-financed BTC purchases. +- Annual results tables summarizing budget balance, debt stock, BTC purchases (initial program, surplus, debt issuance), reserve allocations, and key ratios (debt-to-GDP, BTC % of reserves). +- 2045 comparison grids showing reserves, BTC holdings, BTC %, debt position, and CAGR for all strategies and saved scenarios. +- Chart suite per nation: (1) 21-year reserves vs. BTC holdings combo chart, (2) 2045 reserve comparison, (3) 2045 BTC holdings comparison, (4) optional deficit vs. BTC purchases timeline mirroring workbook charts. +- Guided flow integration (Step 3 of 3 for nation-state path) with navigation footer, scenario persistence controls, CSV export, and validation summary. + +## 3. Non-Goals +- Modeling intragovernmental transfer mechanics beyond workbook scope (e.g., social security trust accounting). +- Real-time macroeconomic data ingestion beyond BTC pricing; inflation/GDP assumptions remain user-driven. +- Implementing policy collaboration or workflow management features (approvals, comments) in initial release. + +## 4. Data & State Dependencies +- Consume normalized nation-state calculation payloads from API endpoints such as `/models/nation/indebted`, `/models/nation/wealthy`, `/models/nation/us` keyed by strategy/scenario. +- Require macro baseline vectors (years, CPI, BTC price path) and fiscal context for hero KPIs. +- Persist edits through the scenario persistence service with optimistic updates, conflict handling, and audit trail. +- Subscribe to guided flow store for stepper state, validation gating, and navigation behavior. +- Leverage shared localization/formatting utilities for currency, trillions/billions abbreviations, and percentage displays. + +## 5. User Experience & Layout +- **Global shell:** Authenticated layout with breadcrumb `Home / Guided Flow / Nation – ` and hero banner containing context text (e.g., U.S. publication reference, debt-to-GDP note). +- **Hero KPI strip:** Cards for Current Year Treasury Assets, 2045 Treasury Assets, 2045 BTC Holdings, BTC % of Reserves, Debt-to-GDP, and Surplus/Deficit delta vs. base strategy. +- **Strategy rail:** Horizontal cards/pills for presets (e.g., Status Quo, Gradual Monetization, Aggressive BTC Reserve, Debt-Funded Accumulation) plus slots for saved custom strategies. +- **Assumption workspace:** + - Collapsible groups: Fiscal Outlook, Treasury Assets, BTC Conversion Program, Surplus Allocation, Debt Strategy, Program Governance. + - Inline charts for sensitivity (e.g., slider linking to quick preview) and tooltips referencing glossary definitions. + - Percentage/currency inputs with formatting, spinner controls, keyboard shortcuts, and accessible labels. +- **Results workspace:** + - Tabbed or sectioned layout for Annual Projections, 2045 Snapshot, Debt Schedule, and Sensitivity (future placeholder). + - Annual table with sticky headers, virtualization for 21 rows, grouped columns for Revenue/Expense, BTC Purchases, Debt Stock, Reserve Composition. + - Snapshot table comparing presets/saved scenarios with toggles for viewing by assets, BTC count, BTC %, or debt metrics. +- **Charts row:** Responsive grid of at least three charts with shared legend, dark-mode ready palette, ability to switch metrics (stacked vs. grouped bars). +- **Insight drawer:** Optional side panel summarizing fiscal takeaways, compliance notes, or recommended actions. +- **Action footer:** Persistent controls for Back (Institution or Macro depending on entry), Next (Completion/Home), Save, Save As, Duplicate, Delete, Export, and validation summary badge. + +## 6. Interaction & Behavior Requirements +1. **Preset switching:** Provide optimistic updates with unsaved-change warnings; log analytics events for strategy selection. +2. **Scenario management:** Integrate scenario persistence drop-down with rename/delete actions and auto-save after debounce. Respect permissions per account. +3. **Validation rules:** Ensure percentages sum to 100%, debt issuance respects caps, surplus allocation cannot exceed available surplus, and treasury assets remain non-negative. Inline errors plus aggregate summary in footer. +4. **Program scheduling:** Support multi-year conversion programs with timeline editor (start/end year), displaying staged BTC purchase results in tables/charts instantly. +5. **Debt toggles:** Enabling debt-funded purchases reveals additional inputs (issuance cap, tenor, rate); disabling prompts to confirm removal of dependent calculations. +6. **Historical context:** Display banner noting start year, live BTC price used, and last fetch timestamp with link to price selector. +7. **Guided flow:** Activate Next button only when required fields validate; returning to Macro or Institution retains edits. Show progress indicator for Step 3 of 3. +8. **Accessibility:** Ensure keyboard navigation across strategy rail, input grids, tables, and charts; provide ARIA annotations for KPI deltas and chart tooltips; maintain WCAG AA contrast. + +## 7. Architecture & Integration +- **Front end:** React/Next.js pages backed by shared layout component and reused table/chart components (task 21). Use Zustand/Redux slices for nation-state state with selectors for each panel. +- **API contracts:** Define TypeScript interfaces mirroring calculation responses; include metadata for validation thresholds and disclaimers. +- **Calculation service:** Extend backend module to compute fiscal projections per nation; reuse macro pricing and debt amortization utilities. Provide deterministic unit tests to match workbook outputs. +- **Caching:** Cache nation responses per scenario; bust cache when assumptions change. Support diff previews for unsaved changes. +- **Analytics:** Emit events for assumption edits, preset selections, validation failures, exports, and navigation transitions. + +## 8. Performance & Resilience +- Virtualize tables for 21-year rows; precompute heavy calculations server-side. Use Suspense/skeletons while data loads. +- Implement retry/backoff for API calls; surface toast on failure with option to retry. Provide offline guardrails (read-only mode with cached scenarios). +- Monitor performance metrics (FCP, TTI) specifically on data-dense screens; ensure charts lazy-load below the fold. + +## 9. Security & Compliance +- Ensure role-based access (future multi-role support) but currently enforce authenticated access only. Sanitize export data and respect content-security policies. +- Mask sensitive fiscal assumptions only if flagged (e.g., non-public data). Log audit entries for assumption edits. + +## 10. Testing Strategy +- **Unit tests:** Validate UI components (form validation, table summaries), utility formatters, and API selectors. +- **Integration tests:** Playwright flows for each nation: preset switch, assumption edit, validation error, save scenario, export. +- **Contract tests:** Ensure backend responses match agreed schemas, including validation metadata. +- **Regression tests:** Snapshot comparisons vs. Excel baseline for key scenarios (year 0 and 2045 metrics). + +## 11. Analytics & Telemetry +- Track events: `nation_preset_selected`, `nation_assumption_changed`, `nation_validation_error`, `nation_scenario_saved`, `nation_export_triggered`, `nation_navigation_next/back`. +- Capture performance metrics per screen (TTI, hydration time). Add heatmap/scroll depth analytics for layout tuning. + +## 12. Rollout & Milestones +1. Finalize API contracts and data normalization (with calculation team). +2. Build shared components required (task 21) and integrate into Indebted Nation screen as pilot. +3. Expand to Wealthy Nation using same components; incorporate nation-specific copy and defaults. +4. Implement United States screen with publication banner and additional charts. +5. Conduct QA regression (unit + Playwright + workbook comparison). +6. Beta release to internal stakeholders; gather feedback. +7. Harden telemetry, address feedback, and promote to production. + +## 13. Open Questions +- Do we require localization for currency (e.g., USD vs. EUR) per nation? If yes, integrate i18n early. +- Should debt issuance modeling include variable-rate instruments or remain fixed-rate as in workbook? +- Do we expose scenario sharing/export to PDF for policy briefings in MVP? diff --git a/docs/onboarding_wizard.md b/docs/onboarding_wizard.md new file mode 100644 index 0000000..1e657da --- /dev/null +++ b/docs/onboarding_wizard.md @@ -0,0 +1,89 @@ +# Onboarding Wizard Implementation Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +This blueprint supports tasks **7** and **14** of the [Development Plan](./development_plan.md), guiding the onboarding journey and live price selection that bridge the cover screen and authenticated experience. + +This document details how to implement the "Get Started" onboarding wizard that shepherds new and returning users from the cover screen through account creation and into the authenticated home experience. It extends the flow behaviors defined in [docs/flow_specific_ux_interactions.md](./flow_specific_ux_interactions.md) and leverages the visual and technical foundations captured in the [design system](./design_system.md) and [theming & motion framework](./theming_motion_framework.md). + +Scenario creation and persistence responsibilities referenced here should follow the API and UX patterns described in the [Scenario Persistence Controls](./scenario_persistence_controls.md) blueprint. + +This document details how to implement the "Get Started" onboarding wizard that shepherds new and returning users from the cover screen through account creation and into the authenticated home experience. It extends the flow behaviors defined in [docs/flow_specific_ux_interactions.md](./flow_specific_ux_interactions.md) and leverages the visual and technical foundations captured in the [design system](./design_system.md) and [theming & motion framework](./theming_motion_framework.md). + +## 1. Goals & Non-Goals +- **Goals** + - Provide a polished, low-friction account creation/sign-in experience that mirrors the Microstrategist-inspired aesthetic. + - Capture the user's preferred starting BTC price (live, historical, or custom) before they reach the home dashboard. + - Persist partial progress and gracefully handle validation or network errors without forcing users to restart the flow. + - Support accessibility, localization readiness, and analytics instrumentation from day one. +- **Non-Goals** + - Implement full profile management or MFA (defer to future account settings work). + - Replace standalone auth routes; wizard reuses underlying auth endpoints defined in [authentication & account persistence](./authentication_account_persistence.md). + +## 2. Architecture Overview +- **Route Structure**: Implement `/onboarding` as a protected public route that becomes available from the Cover page CTA. Use Next.js nested routes to render a full-screen wizard layout. +- **State Management**: Store wizard state in a dedicated Zustand slice (`useOnboardingStore`) with persistence to `sessionStorage` so progress survives refreshes within the session. Mirror the canonical auth state managed by the shared store defined in [shared app foundation](./shared_app_foundation.md). +- **Data Dependencies**: + - Fetch live BTC price via React Query using the pricing service outlined in the [Dynamic Base-Year Handling](./dynamic_base_year_handling.md) plan and the forthcoming external BTC price feed integration guide once those modules land. + - Use MSW mocks in development/test environments to decouple the wizard from real APIs. +- **Navigation**: On successful completion, redirect to `/home` with query flag `?onboarding=complete` for analytics. + +## 3. Step-by-Step Flow +1. **Welcome** + - Hero messaging, key value props, progress indicator set to 1/3. + - Buttons: "Create account" (primary), "Sign in" (secondary text), "Back to cover". +2. **Account Setup / Sign-In** + - Tabs or segmented control to switch between create/sign-in modes. + - Inputs: username, email (optional), password, confirm password (create mode only). + - Inline validation via Zod; password strength meter, show/hide toggle. + - API interactions call `/auth/signup` or `/auth/login` from the auth blueprint, displaying loading state on submit. +3. **Starting Price Selection & Confirmation** + - Display live BTC price card with timestamp + source. + - Radio options: Use live price, Pick historical snapshot (dropdown/calendar), Enter custom (numeric input with currency formatting). + - Summary panel recapping account and price choice. + - Final CTA "Enter dashboard" triggers scenario seed creation and navigation. + +## 4. Visual & Motion Specifications +- **Layout**: Full-height wizard with blurred/glassmorphism backdrop, responsive two-column design on desktop (form + narrative panel) collapsing to single column on mobile. +- **Motion**: Use Framer Motion variants defined in the theming/motion guide for slide transitions (`spring` easing), step progress bar animations, and success confetti micro-interaction (optional) on completion. +- **Components**: Reuse global CTA button styles, input fields, and toasts defined in the design system. Integrate skeleton loaders for price fetch waiting states. +- **Accessibility**: Respect `prefers-reduced-motion`, provide descriptive ARIA labels, ensure focus traps within the modal context, and include keyboard shortcuts (Next `Enter`, Back `Shift+Tab` from first focusable element). + +## 5. Error Handling & Resilience +- Inline error banners for validation issues, non-blocking toast for network failures with retry CTA. +- Preserve user-entered data on errors; do not clear fields unless explicitly requested. +- Implement exponential backoff (up to 3 attempts) for price fetch; fallback to cached latest price if live call fails. +- Show maintenance messaging if auth service is unavailable, with link to status page. + +## 6. Security & Compliance Considerations +- Ensure password fields use `type="password"` with no autocomplete for confirmation field; allow credential manager autofill on primary password and username fields. +- Use reCAPTCHA or hCaptcha toggle flag for bot protection (optional but planned per security backlog). +- Rate-limit sign-up attempts via backend; surface user-friendly error copy when limits are exceeded. +- Log auth errors and onboarding completions to observability stack specified in [performance & accessibility standards](./performance_accessibility_standards.md). + +## 7. Analytics & Success Metrics +- Fire analytics events for each step start/completion, error occurrences, and final wizard success. +- Track drop-off rate per step, time-to-complete, and percentage selecting live vs. historical vs. custom price. +- Link events to user IDs post-authentication for cohort analysis while respecting privacy policies. + +## 8. QA Strategy +- **Unit Tests**: Cover state store reducers/actions, price selection logic, validation schema edge cases. +- **Component Tests**: Use React Testing Library + MSW to validate step transitions, error displays, and successful redirects. +- **E2E Tests**: Playwright scripts simulating create account, sign-in, and cancellation flows on desktop and tablet breakpoints. +- **Accessibility Audits**: Axe + manual keyboard testing for focus order, screen reader labels, and motion preferences. + +## 9. Delivery Milestones +1. Scaffold Next.js route + layout shell with skeleton UI. +2. Implement auth step wiring (forms, API calls, error states). +3. Integrate live price selection UI with mocked API responses. +4. Add persistence, analytics hooks, and polish (motion, accessibility tweaks). +5. Final QA pass across unit/component/e2e suites and handoff to design for sign-off. + +## 10. Open Questions / Follow-Ups +- Confirm copywriting for marketing panel and error states (coordinate with content lead). +- Align on whether historical price picker should support arbitrary dates or limited presets. +- Decide if wizard should be skippable for returning signed-in users (default assumption: skip if session is valid). +- Coordinate launch sequence with marketing assets on Cover screen. + +Refer to the broader blueprints for [authentication](./authentication_account_persistence.md), [shared foundation](./shared_app_foundation.md), [UX flows](./flow_specific_ux_interactions.md), and [performance standards](./performance_accessibility_standards.md) to ensure implementation remains aligned across architecture, experience, and quality guardrails. diff --git a/docs/performance_accessibility_standards.md b/docs/performance_accessibility_standards.md new file mode 100644 index 0000000..3bfb4ee --- /dev/null +++ b/docs/performance_accessibility_standards.md @@ -0,0 +1,61 @@ +# Performance, Accessibility, and Quality Standards Plan + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +This plan fulfills task **4** of the [Development Plan](./development_plan.md), defining the performance, accessibility, and observability guardrails that every subsequent delivery must satisfy. + +This guide details how to operationalize the "snappy" experience mandate for the Bitcoin Model web application. It covers build-time and run-time optimizations, accessibility enforcement, automated quality checks, and observability so the product consistently meets the expectations set by the design system, shared app foundation, UX flow plans, and the authenticated home experience blueprint. + +## Objectives +1. Deliver sub-2s Largest Contentful Paint (LCP) and maintain smooth interactions across target devices. +2. Achieve and retain WCAG 2.1 AA accessibility compliance, including support for assistive technologies and reduced-motion preferences. +3. Provide reliable monitoring, analytics, and automated regression checks that alert the team when performance or accessibility drifts. + +## Key Tooling & Services +- **Performance Audits:** Lighthouse CI, WebPageTest, and Vercel Analytics for real-user monitoring (RUM). +- **Accessibility Testing:** Axe CLI, Storybook a11y add-on, and Playwright tests with `@axe-core/playwright` integration. +- **Static Analysis:** ESLint (with performance-focused rules), TypeScript, Stylelint, and Tailwind IntelliSense to enforce best practices. +- **Monitoring & Logging:** Sentry for frontend error tracking, OpenTelemetry + Grafana for backend metrics, and Logflare (or equivalent) for structured logs. +- **CI/CD Pipeline:** GitHub Actions orchestrating linting, tests, Lighthouse, Axe, and bundle-size checks before merging. + +## Implementation Roadmap + +### 1. Build-Time Optimization +- Enable Next.js Image Optimization, font optimization, and route-based code splitting; audit bundle analyzer output monthly. +- Configure Tailwind `content` paths to purge unused styles and enable the `@tailwindcss/container-queries` plugin only where needed. +- Establish a `performance-budget.json` defining max LCP (2000ms), CLS (0.1), and JS bundle sizes (< 220kB per route after gzip). +- Add ESLint plugins (`eslint-plugin-react-perf`, `eslint-plugin-import`) to catch anti-patterns such as unnecessary re-renders or large synchronous imports. + +### 2. Runtime Performance & UX Feedback +- Implement React Query caching with stale-while-revalidate policies for BTC pricing, scenarios, and macro outputs. +- Prefetch critical routes (BTC, Macro, user models) using Next.js `prefetch` and set up skeleton loaders with Framer Motion shimmer effects. +- Use `IntersectionObserver` hooks to lazy-load heavy charts/tables only when they enter the viewport. +- Instrument web vitals reporting (LCP, FID, CLS, INP) and forward metrics to Vercel Analytics and Sentry for alerting when thresholds are exceeded. + +### 3. Accessibility Safeguards +- Establish global focus-visible styles and ensure interactive components pass manual keyboard navigation audits. +- Configure automated Axe scans in Storybook and Playwright smoke tests; block CI if violations exceed severity thresholds. +- Provide accessible alternatives for charts (data tables, aria-labels, and descriptive summaries) and honor `prefers-reduced-motion` by disabling non-essential animations. +- Incorporate content checks: semantic heading hierarchy, sufficient color contrast (via Tailwind plugins), and form validation with ARIA live regions. + +### 4. Continuous Quality & Observability +- Wire GitHub Actions to run: unit tests (`pnpm test`), type checks (`pnpm typecheck`), linting, Lighthouse CI against staging builds, and Axe CLI. See `.github/workflows/ci.yml` for the authoritative implementation; failing any gate blocks merges. +- Capture backend metrics (API latency, error rate, job success) via OpenTelemetry exporters and visualize in Grafana dashboards. +- Set SLOs: API P95 latency < 400ms, price ingestion job success ≥ 99%, auth success rate ≥ 99.5%. +- Provide runbooks in the ops wiki covering incident response for degraded performance, accessibility regressions, and third-party outages. + +## Deliverables +- CI configuration enforcing performance budgets, accessibility gates, and bundle-size checks. Refer to: + - `.github/workflows/ci.yml` – orchestrates linting, type-checking, unit, Playwright, Lighthouse, Axe, and metrics guardrails. + - `lighthouserc.json` – codifies LCP/accessibility budgets for automated Lighthouse assertions. + - `config/metrics/slo.json` – stores the canonical thresholds for LCP, accessibility, and API latency used by telemetry. + - `scripts/check-metrics.ts` – compares captured telemetry snapshots to the published SLOs during CI. +- Documentation for performance budgets, monitoring dashboards, and accessibility testing workflows. +- Dashboards and alerting rules in Sentry/Vercel/Grafana with on-call notification routing. + +## Success Metrics +- Lighthouse performance ≥ 90 and accessibility ≥ 95 on the authenticated home and BTC model routes. +- No high-severity Axe violations in CI for main branches. +- Real-user monitoring shows LCP < 2s and INP < 200ms for the 75th percentile of sessions. +- Time to detect and resolve performance regressions (MTTD/MTTR) < 1 hour during business hours. diff --git a/docs/route_guarding_navigation.md b/docs/route_guarding_navigation.md new file mode 100644 index 0000000..f616028 --- /dev/null +++ b/docs/route_guarding_navigation.md @@ -0,0 +1,101 @@ +# Bitcoin24 Web App – Route Guarding & Global Navigation + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #9 – Route guarding & global navigation. +- **Dependencies:** Auth services & session persistence ([Authentication & Account Persistence](./authentication_account_persistence.md)), scenario management APIs ([Scenario Persistence Controls](./scenario_persistence_controls.md)), onboarding wizard ([Onboarding Wizard Blueprint](./onboarding_wizard.md)), and user home experience ([User Home Page Blueprint](./user_home_page.md)). +- **Downstream impact:** Guided model flows, scenario persistence controls, and all modeling screens rely on reliable navigation and access control cues. + +## Objectives +1. Ensure only authenticated users can access protected modeling routes while allowing guests to explore public marketing content. +2. Provide a consistent navigation shell (header, sidebar, breadcrumbs) that reflects the authenticated state, highlights current step, and surfaces quick actions. +3. Maintain a responsive, accessible, and performant navigation experience that matches the modern design language defined in the design system and theming guides. + +## Scope +- Client-side routing configuration (Next.js App Router or equivalent) including public vs. protected route segmentation. +- Session verification, token refresh, and optimistic UI around auth status. +- Global navigation components (top bar, collapsible side nav, breadcrumbs, footer quick links). +- Loading states, skeletons, and transitions used when authentication checks are in progress. +- Access control behavior for deep links, expired sessions, and permission edge cases. + +## Non-Goals +- Implementing the auth API itself (covered by the authentication blueprint). +- Detailing the individual modeling screen layouts (covered in upcoming UI specs). +- Designing analytics instrumentation beyond navigation-specific events (see [Performance & Accessibility Standards](./performance_accessibility_standards.md)). + +## Functional Requirements +1. **Route segmentation** + - Public routes: `/`, `/onboarding`, `/legal`, password reset, health pages. + - Protected routes: `/home`, `/btc`, `/macro`, `/models/*`, scenario management endpoints. + - Attempting to access protected routes without a valid session triggers redirect to onboarding/login while preserving the intended destination for post-login routing. +2. **Session verification workflow** + - On initial load, hydrate auth state from secure storage (httpOnly cookie) and call `/auth/session` to validate token. + - While verification occurs, display branded skeleton header and progress indicator; no protected content should flash. + - If verification fails, clear local state and redirect to onboarding with contextual message. + - Refresh tokens automatically in the background before expiry; retry gracefully on transient network errors. +3. **Navigation shell** + - Header contains logo, environment badge (if non-prod), user avatar menu, notifications (future), and “Save Scenario” shortcut when applicable. + - Left rail (collapsible) lists the guided flow steps (BTC, Macro, chosen model), scenario library, and admin/settings entry points. + - Breadcrumb trail appears below the header for screens deeper than level 1, enabling quick return to the home dashboard. + - Active route states visually match theming tokens (e.g., highlight color, glow) and respond to hover/focus per accessibility guidelines. +4. **Guided flow integration** + - Navigation rail dynamically highlights the current flow step and indicates completion with checkmarks. + - “Next” and “Previous” buttons anchor at the footer for sequential progression, updating the router on click. + - When a flow requires prerequisite data (e.g., Macro screen needs BTC assumptions), guard entry with modal explaining missing steps and offering to auto-navigate to prerequisite. +5. **Responsive behavior** + - Below 1024px width, collapse the sidebar into a slide-out drawer accessible via hamburger button. + - Ensure all navigation controls are keyboard operable and screen reader-friendly. + - Maintain 60fps animations when toggling sidebar or switching routes using Framer Motion primitives defined in the theming guide. +6. **Error and edge cases** + - If session expires mid-interaction, display toast + modal prompting re-authentication; unsaved form data should persist in memory/local storage until session restored. + - Handle 403 (forbidden) responses by showing dedicated access-denied screen with support links. + - Provide offline fallback messaging and retry controls if auth checks fail due to network loss. + +## Architectural Decisions +- **Router**: Use Next.js App Router (file-based). Public routes live under `app/(public)/`, protected routes under `app/(protected)/` with a higher-order `ProtectedLayout` that performs session checks. +- **State management**: Leverage Zustand or Redux Toolkit slice `auth` to store session status (`unknown`, `loading`, `authenticated`, `unauthenticated`) and user profile. Persist minimal metadata (user id, name) in memory only; rely on cookies for tokens. +- **Server-side protection**: Implement middleware in Next.js (`middleware.ts`) that intercepts protected routes and performs lightweight session validation (e.g., cookie presence) before allowing render. Redirect to `/onboarding` if missing. +- **Prefetching**: Use Next.js `prefetch` for primary navigation links when session is valid to keep transitions snappy. Disable prefetch for unauthenticated users to avoid unnecessary protected fetches. +- **API hooks**: Encapsulate session validation and refresh logic in reusable hooks (`useSession`, `useRequireAuth`) with suspense integration for declarative loading states. + +## UX & Visual Implementation +- Align header/rail styling with the glassmorphism and dark theme tokens defined in the [Design System & Tech Stack](./design_system.md) and [Theming & Motion Framework](./theming_motion_framework.md). +- Apply motion curves from the theming guide for sidebar slide-in/out and breadcrumb transitions. +- Provide inline tooltips explaining nav icons on hover/focus, sourced from copy guidelines in the UX interactions plan. +- Use skeleton loaders that mimic the shape of the navigation items during auth state hydration. + +## Accessibility & Compliance +- All navigation components must achieve WCAG AA contrast ratios. +- Provide skip-to-content link at the top of the page. +- Ensure focus trapping within the mobile drawer and restore focus to triggering control on close. +- Announce route changes to screen readers using `aria-live` regions or Next.js `next/head` title updates. + +## Performance Considerations +- Lazy-load the scenario library drawer and any secondary navigation modules after the primary layout renders. +- Cache `/auth/session` responses for the duration of the session using SWR or React Query with stale-while-revalidate to minimize network chatter. +- Monitor navigation latency via Web Vitals instrumentation (TTFB, FID) as outlined in the performance standards document. + +## Security Requirements +- Ensure route guards do not expose sensitive data in HTML during SSR; protect using server-side session checks before data fetching. +- Sanitize redirect targets to prevent open redirect vulnerabilities (only allow internal paths). +- Log auth guard failures with correlation IDs for audit, but avoid leaking PII in client logs. + +## Testing Strategy +- **Unit**: Cover `useSession`, auth slice reducers, and guard utilities with Vitest/Jest. +- **Integration**: Use Playwright to verify redirect flows (unauthenticated access, expired sessions, mobile drawer interactions). +- **Accessibility**: Run Axe against navigation pages and ensure focus order/screen reader announcements behave as expected. +- **Performance**: Add Lighthouse CI budget thresholds for navigation pages (LCP < 2.5s, CLS < 0.1). + +## Delivery Milestones +1. Implement foundational guard scaffolding (`ProtectedLayout`, middleware, session hook). +2. Build global navigation shell (header, sidebar, breadcrumbs) with desktop + mobile variants. +3. Integrate guided flow state and contextual Next/Previous actions. +4. Add edge-case handling (session expiry, offline, forbidden states). +5. Finalize accessibility and performance tuning with automated checks wired into CI. + +## Open Questions +- Should we differentiate roles (e.g., admin vs. standard user) at launch, or treat all authenticated users the same? +- Do we need multi-workspace navigation (e.g., teams) in v1, or is a single personal workspace sufficient? +- What telemetry events are required for navigation analytics beyond those noted in the performance guide? + diff --git a/docs/scenario_persistence_controls.md b/docs/scenario_persistence_controls.md new file mode 100644 index 0000000..5ea1eb2 --- /dev/null +++ b/docs/scenario_persistence_controls.md @@ -0,0 +1,93 @@ +# Scenario Persistence Controls Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +This blueprint fulfills task **11** of the [Development Plan](./development_plan.md). It extends the authentication platform described in the [Authentication & Account Persistence](./authentication_account_persistence.md) guide and informs the UX flows outlined in the [User Home Page](./user_home_page.md) and [Guided Model Flow](./guided_model_flow.md) documents. + +## 1. Goals & Success Metrics +- **Trustworthy saves:** Users must never lose edits when moving between BTC → Macro → model screens. Auto-save cycles should finish in <400 ms and surface confirmation toasts when manual saves occur. +- **Version awareness:** Every scenario needs human-readable timestamps, change summaries, and the ability to compare against previous snapshots. +- **Multi-device continuity:** Authenticated users should see identical scenario lists on any device within 5 seconds of load thanks to server reconciliation and optimistic local caches. +- **Safety & transparency:** Destructive actions (delete, overwrite) require explicit confirmation and provide undo windows where possible. + +## 2. Scenario Data Model +- **Entities:** + - `Scenario`: root object containing `id`, `user_id`, `name`, `model_type`, `created_at`, `updated_at`, `last_run_version`, `tags`, and `notes`. + - `ScenarioRevision`: immutable snapshots storing `scenario_id`, `revision`, `payload` (JSON of all assumptions + derived states), `created_at`, and `created_by`. + - `ScenarioShare` (future-ready): optional entity to support collaboration/invitations without modifying existing schema. +- **Payload Schema:** + - Partition JSON by domain: `{ btc: {...}, macro: {...}, models: {individual: {...}, corporate: {...}, ...} }` to keep calculations modular. + - Include metadata for ARR curves, live price selection, **base-year snapshot** (`baseYear`, `latestDate`, `latestPrice`), and selected presets to rehydrate UI toggles quickly in accordance with the [Dynamic Base-Year Handling](./dynamic_base_year_handling.md) plan. +- **Indices:** composite index on `(user_id, updated_at desc)` for dashboard queries, and `(scenario_id, revision desc)` for revision history retrieval. + +## 3. API Surface +- **REST Endpoints (NestJS /apps/api):** + - `POST /scenarios` – create; validates uniqueness of `name` per user and seeds `ScenarioRevision` revision `1`. + - `GET /scenarios` – list all scenarios for the authenticated user with pagination and optional `model_type` filter. + - `GET /scenarios/:id` – fetch full scenario with latest payload and optionally include revision summaries. + - `PUT /scenarios/:id` – update metadata (`name`, `tags`, `notes`) and initiate a new revision when payload changes. + - `POST /scenarios/:id/revisions` – explicit snapshot endpoint used by auto-save and manual "Save As" actions. + - `DELETE /scenarios/:id` – soft delete by default; purge after retention window. + - `POST /scenarios/:id/duplicate` – create a new scenario with copied payload + incremented name suffix. +- **Realtime Hooks:** employ WebSockets (NestJS `@WebSocketGateway`) or Supabase Realtime channels for cross-tab sync. +- **Validation:** use Zod schemas shared with the front end to guard request bodies and ensure consistent typing. + +## 4. Front-End State Management +- **State store:** Extend the global Zustand store (`/apps/web/src/state/scenarios.ts`) with slices for `scenarios`, `activeScenario`, `pendingChanges`, and `autosaveStatus`. +- **Optimistic updates:** Apply UI updates immediately, queue API requests, and reconcile responses (or roll back) with conflict detection using `updated_at` + revision numbers. +- **Offline handling:** Persist edits to IndexedDB via `idb-keyval`, show an "Offline" badge, and replay queued saves once connectivity returns. +- **Auto-save cadence:** + - Trigger on blur for inputs, on navigation between steps, and every 30 seconds when edits are detected. + - Debounce to avoid flooding the API; cancel outstanding requests if a new edit occurs before completion. + +## 5. User Interface Requirements +- **Scenario toolbar:** Each modeling screen gains a sticky bar with buttons for `Save`, `Save As`, `Duplicate`, `Revert`, and a dropdown to switch scenarios. +- **Confirmation modals:** Utilize the shared modal component with secondary text describing consequences; destructive actions demand typing the scenario name to confirm. +- **Revision history drawer:** Slide-over panel listing timestamps, authors, change summaries, and quick actions (`Restore`, `Compare`). +- **Comparison view:** Display diff tables highlighting changed assumptions and KPIs between two revisions; rely on highlight colors defined in the theming guide. +- **Toast feedback:** `Success`, `Warning`, `Error` states mapped to the notification palette; include "View revision" link on successful saves. +- **Loading skeletons:** When fetching scenario lists, use card skeletons consistent with the home page blueprint to preserve perceived performance. + +## 6. Integration with Guided Flow & Onboarding +- **Onboarding:** After account creation, present a "Create your first scenario" wizard step that seeds defaults and performs the initial `POST /scenarios` call. +- **Guided flow:** Navigating BTC → Macro → model automatically saves context before and after each step, ensuring the progress indicator reflects the latest persisted state and that base-year metadata stays in sync when users accept newer historical data. +- **Home dashboard:** Scenario cards show last-updated timestamps, tags, and quick actions (Continue, Duplicate, Delete). Respect filters (`model_type`, `tag`). + +## 7. Security & Compliance +- Require auth middleware (`JwtAuthGuard`) for all scenario routes; enforce user scoping in Prisma queries. +- Implement per-user quotas (default 20 active scenarios) with graceful messaging when limits are reached. +- Log all create/update/delete events with audit metadata (ip, user agent) for compliance review. +- Encrypt sensitive fields at rest if infrastructure supports it; at minimum ensure database backups are encrypted. + +## 8. Performance & Reliability +- API endpoints must respond within 250 ms p95 under normal load with Postgres connection pooling (pgBouncer) and Redis caching for read-heavy endpoints. +- Employ background workers for heavy diffing/comparison if payloads exceed 200 KB to keep UI responsive. +- Add retries with exponential backoff for failed auto-save calls; surface toast warnings after three consecutive failures. +- Monitor key metrics: save success rate, average auto-save latency, duplication frequency, and error codes per user. + +## 9. QA Strategy +- **Unit tests:** + - Backend: Prisma model tests + service tests validating CRUD logic, revision creation, and access control. + - Front end: Zustand store reducers/selectors, auto-save hooks, and optimistic update behavior using Vitest + React Testing Library. +- **Integration tests:** + - Playwright flows covering create → edit → duplicate → delete → restore. + - Contract tests between front end Zod schemas and backend DTOs. +- **Load testing:** k6 script simulating concurrent saves to confirm API throughput and contention behavior. +- **Manual QA:** Checklist verifying offline edits, conflict resolution modals, accessibility of all controls, and localization readiness. + +## 10. Delivery Milestones +1. **Schema & API implementation (Backend squad)** – 3 story points; produce Prisma migrations, NestJS controllers/services, and unit coverage. +2. **Front-end state & UI scaffolding (Web squad)** – 5 story points; deliver toolbar, store slices, and auto-save plumbing behind feature flags. +3. **Revision history & comparison (Shared feature)** – 3 story points; implement drawer UI, diff rendering, and restore flows. +4. **Polish & QA hardening** – 2 story points; finalize analytics, error messaging, load testing, and accessibility audits. + +## 11. Dependencies & Open Questions +- Depends on: completed auth infrastructure, navigation guards, onboarding/home UX per their respective blueprints. +- Requires alignment with dynamic pricing tasks (12–14) to ensure payload schema captures live price sources. +- Open questions: + - Should we support scenario sharing at MVP or defer to a later milestone? + - What retention period applies to soft-deleted scenarios before permanent purge? + - Do we need export/import (JSON/CSV) for compliance or backups in phase one? + +Keeping this blueprint synchronized with the [Development Plan](./development_plan.md) ensures all teams share a single source of truth for scenario persistence work. diff --git a/docs/shared_app_foundation.md b/docs/shared_app_foundation.md new file mode 100644 index 0000000..b275608 --- /dev/null +++ b/docs/shared_app_foundation.md @@ -0,0 +1,74 @@ +# Shared Application Foundation Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +This blueprint corresponds to task **5** of the [Development Plan](./development_plan.md), establishing the repository structure, data ingestion, and service architecture that underpin all subsequent milestones. + +This document translates the "Set up shared app foundation" task into a concrete implementation roadmap. It focuses on scaffolding the codebase, ingesting workbook data, reproducing Excel logic, and preparing persistence/state layers that power every screen in the Bitcoin24 web app. + +## 1. Repository Structure & Tooling +- **Monorepo layout:** + - `/apps/web` – Next.js front end (SPA/SSR hybrid) consuming the shared model API. + - `/apps/api` – NestJS or Fastify service exposing REST/GraphQL endpoints for auth, scenarios, and model computations. + - `/packages/models` – Pure TypeScript calculation library translating Excel formulas into testable functions. + - `/packages/ui` – Shared UI kit (KPI cards, tables, charts) published via Storybook. + - `/packages/config` – ESLint/Prettier/Tailwind configs shared across apps. +- **Dev tooling:** pnpm workspace, TurboRepo for task orchestration, Git hooks (lint-staged + Husky) enforcing formatting and type safety before commits. +- **Testing baseline:** Vitest/Jest for unit tests, Playwright for integration/smoke tests, and msw for mocking API calls. + +## 2. Workbook Data Ingestion +- **Extraction script:** Node/TypeScript script using `xlsx` or `SheetJS` to parse `Bitcoin24 v1.0.xlsm` and export JSON fixtures. +- **Outputs:** + - `macro_defaults.json` – baseline macro assumptions, year vector, scenario presets. + - `btc_defaults.json` – price presets, ARR schedules, KPI targets. + - `individual/corporate/institution/nation` assumption tables for each strategy preset. +- **Versioning:** Store raw dumps under `/packages/models/fixtures/.json` with schema definitions (Zod) to validate integrity. +- **Automation:** Add npm script `pnpm ingest:workbook` to regenerate fixtures whenever the Excel file updates. + +## 3. Calculation Engine +- **Goal:** Recreate Excel formulas as deterministic TypeScript functions residing in `/packages/models`. +- **Modules:** + - `time-series` – handles date vectors, CAGR/ARR calculations, interpolation. + - `macro` – inflation, innovation, inefficiency, asset conversion, debt issuance. + - `btc` – scenario presets, price path calculations, KPI summarization. + - `micro` – individual/corporate/institution models with shared helpers for taxation, BTC purchases, leverage. + - `nation` – fiscal projections, treasury conversions, debt schedules. +- **Design:** Functions accept typed inputs (validated via Zod), return normalized outputs (tables, KPI summaries, chart-ready arrays). +- **Testing:** Unit tests covering core formulas with regression fixtures to match Excel outputs, plus snapshot tests for cross-sheet dependencies. + +## 4. API & Persistence Layer +- **Database schema (PostgreSQL via Prisma):** + - `users` (id, username, password_hash, created_at, updated_at). + - `scenarios` (id, user_id FK, name, model_type, assumptions JSONB, created_at, updated_at). + - `btc_prices` (id, date, price_usd, source, fetched_at). +- **Services:** + - Auth service (sign-up, login, session refresh, password change). + - Scenario service (CRUD operations, duplication, version history). + - Pricing service (latest price retrieval, historical series). + - Model service (executes calculation engine functions with inputs from scenarios/defaults). +- **API surface:** REST + optional GraphQL overlay; all protected endpoints enforce JWT/httpOnly cookie auth. +- **Background jobs:** Scheduled fetch of BTC price (CoinGecko/Coinbase) via serverless cron writing to `btc_prices` table. + +## 5. Front-End State Management +- **Global stores:** + - Auth store (user profile, session tokens, derived permissions). + - Scenario store (active scenario metadata, dirty state, save status). + - Pricing store (latest price, historical cache, data freshness timestamp). +- **Data fetching:** React Query hooks for API interaction with optimistic updates and cache invalidation tied to scenario IDs. +- **Persistence:** Auto-save to server when users navigate between flow steps; optional localStorage draft layer for offline resilience. +- **Routing guards:** Higher-order components that redirect unauthenticated users to onboarding and preload required data for protected routes. + +## 6. Developer Experience & CI/CD +- **Local DX:** `pnpm dev` command spins up both web and API apps with hot reloading; docker-compose optional for PostgreSQL. +- **CI pipeline:** GitHub Actions running lint → type-check → unit tests → Playwright smoke → Lighthouse/Axe audits for PRs. +- **Preview environments:** Vercel previews for the web app; Render/Fly staging for the API seeded with anonymized fixture data. +- **Documentation:** Docusaurus site under `/docs` mirroring these guides, with architectural diagrams (C4 or similar) generated via PlantUML. + +## 7. Success Criteria +- Monorepo bootstraps in <5 minutes on a fresh machine with `pnpm install`. +- Fixture ingestion reproduces Excel defaults with ±0.1% tolerance across KPI outputs. +- API endpoints achieve p95 latency < 300ms under load test of 50 RPS. +- End-to-end smoke test (create account → load BTC model) passes in CI within 5 minutes. + +This foundation ensures that subsequent tasks—auth, onboarding flows, dynamic pricing, and model screens—build atop a stable, well-tested core mirroring the original workbook logic. Pair these architectural steps with the [Design System & Tech Stack](./design_system.md), [Theming & Motion Framework](./theming_motion_framework.md), [Flow-Specific UX Interactions](./flow_specific_ux_interactions.md), [Authentication & Account Persistence Blueprint](./authentication_account_persistence.md), [Dynamic Base-Year Handling](./dynamic_base_year_handling.md), [External BTC Price Ingestion](./external_btc_price_ingestion.md), and [Performance & Accessibility Standards](./performance_accessibility_standards.md) documents—along with the [Onboarding Wizard Implementation Blueprint](./onboarding_wizard.md) and [User Home Page Implementation Blueprint](./user_home_page.md)—to deliver a cohesive product. diff --git a/docs/shared_ui_component_library.md b/docs/shared_ui_component_library.md new file mode 100644 index 0000000..3abc4ef --- /dev/null +++ b/docs/shared_ui_component_library.md @@ -0,0 +1,83 @@ +# Bitcoin24 Web App – Shared UI Component Library Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +- **Development Plan task:** #21 – Shared UI component library. +- **Upstream dependencies:** Design system tokens ([Design System & Tech Stack](./design_system.md)), theming/motion framework ([Theming & Motion Framework](./theming_motion_framework.md)), UX interaction patterns ([Flow-Specific UX Interactions](./flow_specific_ux_interactions.md)), and screen blueprints (BTC, Macro, micro models, Nation-state). +- **Downstream impact:** Enables rapid implementation of remaining screens, ensures consistent accessibility/performance, and feeds validation/guidance instrumentation (task 22). + +## 1. Objectives +1. Deliver a reusable component suite aligned with the Bitcoin24 visual language, covering layout primitives, form controls, tables, charts, navigation, and feedback patterns. +2. Centralize accessibility, motion, and responsive behaviors to avoid duplicative implementations across screens. +3. Support design token-driven theming (light/dark), animation presets, and data visualization palettes consistent with Microstrategist-inspired aesthetics. +4. Provide documentation, Storybook coverage, and testing harnesses so product teams can adopt components confidently. + +## 2. Scope +- **Foundations:** Typography, spacing, color utilities, elevation, shadows, gradients exposed via Tailwind plugins or CSS variables. +- **Layout primitives:** Page shell, section containers, grids, split panels, sticky headers/footers. +- **Navigation elements:** Breadcrumbs, steppers, tabs, pill selectors, action footers, global header with auth state. +- **Form controls:** Text/number inputs, sliders, segmented controls, dropdowns, toggles, date pickers, inline validation messaging, tooltips. +- **Data display:** KPI cards, statistic tiles, accordions, collapsible panels, alert banners, badge chips, toast notifications. +- **Tables:** Virtualized data grid with column pinning, grouping, sort/filter, inline editing, skeleton loading. +- **Charts:** Wrapper around chosen charting library (Recharts/ECharts) with Bitcoin24 theme, responsive sizing, shared legend, export actions. +- **Feedback:** Skeletons, shimmer loaders, progress bars, success/error toasts, modal dialogs. +- **Utilities:** Formatting helpers (currency, percentages, abbreviations), analytics event helpers, keyboard focus management hooks. + +## 3. Non-Goals +- Building domain-specific components that belong inside individual screens (e.g., bespoke conversion timeline editors) unless they generalize across models. +- Delivering a public design system site; documentation via Storybook/MDX is sufficient for internal use. +- Supporting IE11 or outdated browsers beyond agreed baseline (modern evergreen browsers). + +## 4. Architecture & Tooling +- Component library implemented in TypeScript within the front-end workspace (e.g., `/apps/web/src/components` or `/packages/ui`). +- Storybook configured with dark/light themes, accessibility addons, controls, and Chromatic/visual regression pipeline. +- Testing via Vitest/Jest + React Testing Library; integration snapshots using Storybook testing utilities. +- Linting with ESLint + stylelint + Tailwind lint plugin; enforce design token usage via custom ESLint rules where possible. +- Documentation in MDX per component, linking to usage guidelines and interaction notes. + +## 5. Accessibility & Internationalization +- Components must support keyboard navigation, ARIA attributes, focus rings, and high-contrast mode. +- Provide localization hooks (e.g., `aria-label` translation props, formatters) but actual translation strings handled by consumer. +- Validate color contrast per component with automated Axe tests; ensure motion can be reduced when `prefers-reduced-motion` is set. + +## 6. Performance Considerations +- Tree-shakeable exports via barrel files; prefer headless patterns where appropriate to reduce bundle size. +- Lazy-load heavy chart libraries; provide lightweight skeleton wrappers. +- Ensure virtualization for large data tables and avoid excessive re-renders via memoization/hooks discipline. + +## 7. Integration Strategy +1. Audit existing screen blueprints to identify shared needs and prioritize component backlog. +2. Establish naming conventions and file structure (e.g., `components/ui`, `components/data`, `components/layout`). +3. Build foundational primitives first (layout, typography, cards) followed by complex components (data grid, charts). +4. Pair with Macro/BTC screen implementation to validate components in production context. +5. Provide migration guidance for future contributions (e.g., PR checklist ensuring component reuse). + +## 8. Testing Strategy +- **Unit tests:** Props handling, accessibility behavior, conditional rendering, theming variations. +- **Visual regression:** Storybook snapshots (Chromatic/Applitools) for critical components (cards, tables, charts) across themes. +- **Integration:** Smoke tests embedding components within sample pages (guided flow demo) to ensure composition works. +- **Performance tests:** Measure render timing for data grid and chart wrappers with representative datasets. + +## 9. Documentation & Developer Experience +- Maintain Storybook with usage examples tied to real modeling scenarios (e.g., KPI card with BTC price, table with macro projections). +- Provide MDX notes on dos/don’ts, accessibility tips, and theming instructions. +- Publish component changelog and versioning strategy (SemVer within mono-repo) to track breaking changes. +- Offer code generators or snippets for common patterns (e.g., create new card variant) via CLI scripts or Plop. + +## 10. Analytics & Telemetry +- Expose optional hooks for instrumentation (e.g., `onEvent` callbacks) so consumer screens can log interactions consistently. +- Log component-level warnings when misuse detected (e.g., missing required aria labels in dev mode). + +## 11. Rollout Plan +1. Finalize component architecture and tooling configuration. +2. Build MVP set (layout shell, KPI card, button, input, accordion, chart wrapper, data table) and release internally. +3. Integrate components into Macro/BTC screens to validate ergonomics. +4. Expand library for micro/nation screens based on blueprint requirements. +5. Establish contribution guidelines, PR templates, and review checklist focused on reuse/accessibility. +6. Monitor adoption via codebase linting (flag duplicate implementations) and gather developer feedback for iteration. + +## 12. Open Questions +- Do we publish the library as a separate package for potential external usage, or keep it internal-only? +- Should we integrate design token sync with Figma (e.g., via Tokens Studio) in the initial milestone or later? +- How aggressively do we enforce usage (e.g., ESLint bans on raw HTML tags for buttons/inputs outside library)? diff --git a/docs/theming_motion_framework.md b/docs/theming_motion_framework.md new file mode 100644 index 0000000..9a04159 --- /dev/null +++ b/docs/theming_motion_framework.md @@ -0,0 +1,97 @@ +# Shared Theming and Motion Framework Plan + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +This plan addresses tasks **2** and **21** from the [Development Plan](./development_plan.md), detailing the implementation approach for the design tokens, motion presets, and shared UI building blocks referenced across subsequent feature work. + +This document describes how to implement the shared theming layer and motion framework for the Bitcoin Model web application. The goal is to ensure a cohesive look-and-feel, consistent animations, and responsive layouts across all screens described in the product plan. + +## Objectives + +1. Establish a centralized theming system that supports light and dark variants inspired by Microstrategist's aesthetic. +2. Provide reusable primitives for typography, spacing, color, elevation, and glassmorphism effects. +3. Integrate motion primitives using Framer Motion to deliver smooth transitions and interactive feedback at 60fps. +4. Ensure responsive design patterns for data-dense screens across desktop, tablet, and large mobile devices. +5. Bake accessibility (WCAG AA), reduced-motion preferences, and performance considerations into the implementation. + +## Technology Selections + +- **Styling Framework**: Tailwind CSS with a custom configuration layered on top of CSS variables. Tailwind offers utility-first development while CSS variables enable runtime theme toggling. +- **Theme Management**: `next-themes` for light/dark mode switching with system preference detection and persistence. +- **Animation Library**: Framer Motion for page transitions, component-level motion, and shared layout animations. +- **Design Tokens**: Managed via a `theme.config.ts` file exporting color palettes, typography scales, spacing, and radii tokens. +- **Component Library Support**: Headless UI (for accessible primitives) combined with custom components styled via Tailwind classes. + +## Implementation Roadmap + +1. **Tailwind Configuration** + - Extend `tailwind.config.ts` to map design tokens to utility classes (colors, fonts, spacing, shadows, blur). + - Enable JIT mode and configure dark mode to use the `class` strategy. + - Define custom screens: `xl` (1440px), `lg` (1280px), `md` (1024px), `sm` (768px), `xs` (540px). + +2. **Design Token Definition** + - Create `src/theme/tokens.ts` exporting objects for `colors`, `typography`, `spacing`, `radii`, `shadows`, and `glass` overlays. + - Derive colors from the design system palette (BTC orange #F7931A, charcoal #0E1116, slate #1E232C, accent teal #3FE0D0, neutral white #F8FAFC). + - Provide semantic color aliases: `surface`, `surfaceAlt`, `primary`, `accent`, `textPrimary`, `textSecondary`, `border`, `success`, `warning`, `danger`. + - Include gradient definitions for hero sections and CTA buttons (e.g., `linear-gradient(135deg, #F7931A 0%, #3FE0D0 100%)`). + +3. **CSS Variable Layer** + - Generate CSS variables from tokens inside `src/theme/global.css` for both light (`:root`) and dark (`.theme-dark`) scopes. + - Ensure variables include alpha variants for overlays (e.g., `--surface-translucent: rgba(14, 17, 22, 0.72)`). + - Support reduced motion via `@media (prefers-reduced-motion: reduce)` overrides to disable animations gracefully. + +4. **Global Layout Shell** + - Implement `src/components/layout/AppShell.tsx` with glassmorphism header, content container, and responsive side navigation. + - Use Tailwind utilities to apply background gradients, blur, and drop shadows consistent with the design. + - Include theme toggle control tied to `next-themes` and persist the selection. + +5. **Typography System** + - Import the chosen font pairing (e.g., `Satoshi` for headings, `Inter` for body) via Next.js font optimization. + - Define heading/body utility classes (e.g., `.heading-xl`, `.body-md`) mapped to tokens. + - Ensure line-height, letter-spacing, and responsive scaling for readability on wide tables. + +6. **Motion Primitives** + - Create `src/motion/presets.ts` containing shared Framer Motion variants: + - `fadeInUp`, `fadeInScale`, `slideIn`, `staggerContainer`, `glowPulse`. + - Implement route transition wrapper `PageTransition` using `AnimatePresence` to animate route changes. + - Provide component wrappers (`MotionCard`, `MotionButton`) applying subtle hover/press effects. + - Respect reduced-motion preferences by disabling certain animations when `prefers-reduced-motion` is true. + +7. **Responsive Utilities** + - Implement grid helpers (`Grid`, `Grid.Item`) for adaptive layouts in `src/components/layout/Grid.tsx`. + - Add Tailwind plugins for `safe-area` insets, container queries, and fluid typography. + - Configure sticky headers and scroll shadows for long tables using `IntersectionObserver` based hooks. + +8. **Accessibility and Testing** + - Integrate Axe and Storybook accessibility tests to validate color contrast and keyboard navigation. + - Provide focus-visible styles for interactive components. + - Add motion unit tests where feasible (e.g., verifying `prefersReducedMotion` hook disables animations). + +9. **Performance Considerations** + - Ensure Framer Motion components use `layoutId` judiciously to avoid reflow thrashing. + - Prefetch critical fonts and precompute Tailwind classes to minimize runtime CSS. + - Lazy-load animation-heavy components (e.g., chart wrappers) with suspense fallbacks. + +10. **Documentation & Developer Experience** + - Document component usage in Storybook with example states for light/dark themes. + - Provide a theming guide in `docs/design_system.md` referencing this framework for contributors. + - Coordinate with the architectural plan in [docs/shared_app_foundation.md](./shared_app_foundation.md) so UI tokens and motion utilities integrate cleanly with the shared monorepo structure. + - Align component styling for the authenticated hub with the [User Home Page Implementation Blueprint](./user_home_page.md). + - Set up linting rules (Stylelint/ESLint) to enforce naming conventions and consistent utility usage. + +## Deliverables + +- Tailwind configuration files and tokens modules checked into the repo. +- Global stylesheet with CSS variables for light/dark modes. +- Shared motion preset utilities and wrappers. +- Storybook stories demonstrating the theming and motion patterns. +- Documentation updates pointing developers to the shared framework. + +## Success Metrics + +- Page transitions and component interactions render at 60fps on target devices. +- Light and dark modes achieve WCAG AA contrast ratios. +- Layouts remain legible and performant at viewport widths from 540px to 1920px. +- Developer onboarding time is reduced thanks to clear tokens, presets, and documentation. + diff --git a/docs/user_home_page.md b/docs/user_home_page.md new file mode 100644 index 0000000..5bba125 --- /dev/null +++ b/docs/user_home_page.md @@ -0,0 +1,78 @@ +# User Home Page Implementation Blueprint + +[Back to README](../README.md) • [Development Plan](./development_plan.md) + +## Plan Alignment +This blueprint fulfills task **8** of the [Development Plan](./development_plan.md), detailing the authenticated dashboard experience that connects onboarding outcomes with the modeling workflows. + +This document translates task 8 of the roadmap—"Create user Home page"—into an actionable implementation plan. It aligns with the design direction, theming, motion, and flow guides already established for the Bitcoin Model web app. + +Scenario management interactions on the Home experience should follow the patterns defined in the [Scenario Persistence Controls](./scenario_persistence_controls.md) blueprint, while live pricing banners and status affordances rely on the [External BTC Price Ingestion](./external_btc_price_ingestion.md), [Dynamic Base-Year Handling](./dynamic_base_year_handling.md), and [Live Price Onboarding Integration](./live_price_onboarding_integration.md) plans. + +## 1. Objectives & Success Criteria +- Deliver a personalized, data-rich landing experience immediately after onboarding or sign-in. +- Provide clear navigation into the guided BTC → Macro → Model flow while still supporting free exploration. +- Surface saved scenarios, live-price context, and quick actions in a performant, accessible UI. +- Success metrics: Home load < 1.2s on broadband, task completion (launch a model) within 2 clicks, ≥95% Lighthouse accessibility score, and ≥70% of returning users interacting with at least one saved scenario per session. + +## 2. Information Architecture +- **Hero Overview**: Greeting, summary of active scenario, last modified timestamp, and "Resume" CTA. +- **Guided Flow Banner**: Horizontal stepper (BTC → Macro → Models) with progress tracking and deep links. +- **Model Catalog**: Responsive grid of cards grouped by category (Core BTC, Macro Engine, Individuals, Corporate, Institution, Nation-State variants). +- **Saved Scenarios**: Tabbed list (All, Recently Viewed, Favorites) with table rows, filters, and inline actions. +- **Insights & Updates**: Optional announcement card (release notes, price feed status) and checklist for next steps. +- **Support & Resources**: Links to documentation, tutorials, and contact support. + +## 3. Data Dependencies & State +- Fetch authenticated user profile, scenario metadata, and pricing preferences via React Query on route entry. +- Maintain local Zustand slice for UI state (active tab, search filters, card layout mode) to avoid excessive renders. +- Subscribe to pricing context so the banner shows "Starting from $X as of DATE" with edit affordance. +- Ensure scenario actions (save, duplicate, delete) update the optimistic cache and trigger toast confirmations. + +## 4. UI & Interaction Patterns +- **Hero Card**: Glassmorphism surface with subtle gradient, animated avatar initials, and "Resume" button using accent gradient. +- **Guided Flow Stepper**: Framer Motion transitions on progress changes; display completion badges when steps finished. +- **Model Cards**: Hover tilt and glow, status chips (Not started, In progress, Complete), quick action buttons (Start guided, Jump in, View docs). +- **Saved Scenario Table**: Virtualized rows for performance, inline rename via double-click, kebab menu for actions. +- **Search & Filters**: Debounced search input, filter chips (model type, status), and sort dropdown. +- **Empty States**: Friendly illustrations when no scenarios; CTA to "Start guided flow". +- **Notifications**: Toast stack bottom-right for scenario actions; inline warning banner if price feed stale. + +## 5. Responsiveness & Layout +- Desktop: 12-column grid with hero and flow banner spanning top row, cards below in 3–4 column layout. +- Tablet: Collapse left rail navigation into top tabs; cards in 2-column layout; table switches to stacked cards with key metrics. +- Mobile: Stack sections vertically with accordions; hero condenses into condensed card; scenario list uses swipe actions. +- Employ CSS clamp for responsive typography and Tailwind container queries for layout adjustments. + +## 6. Accessibility & Localization +- Semantic landmarks (`
`, `
`, `
+ ); +} + +function getRouteForStep(step: FlowStep): string { + switch (step) { + case 'btc': + return '/models/btc'; + case 'macro': + return '/models/macro'; + case 'model': + return '/models/individual'; + } +} diff --git a/src/components/home/GuidedFlowBanner.tsx b/src/components/home/GuidedFlowBanner.tsx new file mode 100644 index 0000000..795abc5 --- /dev/null +++ b/src/components/home/GuidedFlowBanner.tsx @@ -0,0 +1,42 @@ +'use client'; + +import Link from 'next/link'; +import { ArrowRight, CheckCircle2, Clock } from 'lucide-react'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; + +const steps = [ + { key: 'btc', label: 'BTC assumptions', href: '/models/btc' }, + { key: 'macro', label: 'Macro expansion', href: '/models/macro' }, + { key: 'model', label: 'Sector model', href: '/models/individual' } +] as const; + +export function GuidedFlowBanner() { + const { completion } = useGuidedFlowStore(); + + return ( +
+
+

Guided flow

+

BTC → Macro → Model

+

+ Progress auto-saves every few seconds. Validation warnings surface in the stepper below. +

+
+
+ {steps.map((step) => { + const status = completion[step.key]; + const isDone = status?.status === 'complete'; + return ( + +
+ {isDone ? : } + {step.label} + +
+ + ); + })} +
+
+ ); +} diff --git a/src/components/home/HomeHero.tsx b/src/components/home/HomeHero.tsx new file mode 100644 index 0000000..0023bd2 --- /dev/null +++ b/src/components/home/HomeHero.tsx @@ -0,0 +1,42 @@ +'use client'; + +import { format } from 'date-fns'; +import { useAuthStore } from '@/src/state/authStore'; +import { useScenarioStore } from '@/src/state/scenarioStore'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; + +export function HomeHero() { + const user = useAuthStore((state) => state.user); + const { scenarioId } = useGuidedFlowStore(); + const scenario = useScenarioStore((state) => (scenarioId ? state.scenarios[scenarioId] : undefined)); + + return ( +
+
+

Welcome back

+

+ {user ? `${user.username}, your Bitcoin thesis awaits.` : 'Your Bitcoin thesis awaits.'} +

+

+ Resume where you left off or jump into a new guided flow. Your scenarios auto-save with validation cues to keep + assumptions in sync. +

+ {scenario && ( +
+

Last scenario

+

{scenario.name}

+

Updated {format(new Date(scenario.updatedAt), 'MMM d, yyyy h:mma')}

+
+ )} +
+
+

Fast actions

+
    +
  • • Start guided BTC → Macro → Model flow
  • +
  • • Compare scenarios in the library
  • +
  • • Update live price anchor from onboarding
  • +
+
+
+ ); +} diff --git a/src/components/home/LivePriceBanner.tsx b/src/components/home/LivePriceBanner.tsx new file mode 100644 index 0000000..e553239 --- /dev/null +++ b/src/components/home/LivePriceBanner.tsx @@ -0,0 +1,34 @@ +'use client'; + +import { useLivePrice } from '@/src/hooks/useLivePrice'; +import { formatCurrency } from '@/src/lib/calculations'; +import { AlertTriangle, Loader2, RefreshCcw } from 'lucide-react'; + +export function LivePriceBanner() { + const { data, isFetching, error, refetch } = useLivePrice(); + + return ( +
+
+

Live price

+

+ {data ? formatCurrency(data.price, { maximumFractionDigits: 0 }) : 'Loading price…'} +

+

+ {data ? `Sourced from ${data.source} at ${new Date(data.timestamp).toLocaleTimeString()}` : 'Fetching price feed'} +

+
+
+ {error && ( + + Using cached fallback + + )} + +
+
+ ); +} diff --git a/src/components/home/ModelCatalog.tsx b/src/components/home/ModelCatalog.tsx new file mode 100644 index 0000000..3e5a8a7 --- /dev/null +++ b/src/components/home/ModelCatalog.tsx @@ -0,0 +1,61 @@ +'use client'; + +import Link from 'next/link'; +import { ArrowRight } from 'lucide-react'; + +const models = [ + { + title: 'BTC Core Model', + description: 'Adjust ARR, price trajectories, and live pricing anchors.', + href: '/models/btc' + }, + { + title: 'Macro Engine', + description: 'Translate BTC adoption into global liquidity and GDP effects.', + href: '/models/macro' + }, + { + title: 'Individual Strategy', + description: 'Plan accumulation and runway for households and individuals.', + href: '/models/individual' + }, + { + title: 'Corporate Treasury', + description: 'Calibrate treasury allocations, leverage, and coverage ratios.', + href: '/models/corporate' + }, + { + title: 'Institutional Portfolio', + description: 'Model pension and fund exposure with guardrails and targets.', + href: '/models/institution' + }, + { + title: 'Nation-State Reserves', + description: 'Simulate sovereign adoption, FX reserves, and productivity boosts.', + href: '/models/nation' + } +]; + +export function ModelCatalog() { + return ( +
+
+

Model catalog

+ + Start guided flow + +
+
+ {models.map((model) => ( + +
+

{model.title}

+ +
+

{model.description}

+ + ))} +
+
+ ); +} diff --git a/src/components/home/ScenarioLibrary.tsx b/src/components/home/ScenarioLibrary.tsx new file mode 100644 index 0000000..fff9546 --- /dev/null +++ b/src/components/home/ScenarioLibrary.tsx @@ -0,0 +1,104 @@ +'use client'; + +import { useMemo, useState } from 'react'; +import { useScenarioStore } from '@/src/state/scenarioStore'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; +import { ArrowUpRight, Copy, Trash2 } from 'lucide-react'; +import Link from 'next/link'; +import { formatDistanceToNow } from 'date-fns'; +import clsx from 'clsx'; + +export function ScenarioLibrary() { + const scenarios = useScenarioStore((state) => Object.values(state.scenarios)); + const duplicateScenario = useScenarioStore((state) => state.duplicateScenario); + const deleteScenario = useScenarioStore((state) => state.deleteScenario); + const setActiveScenario = useScenarioStore((state) => state.setActiveScenario); + const setFlowScenario = useGuidedFlowStore((state) => state.setScenario); + const markStep = useGuidedFlowStore((state) => state.markStep); + const [filter, setFilter] = useState<'all' | 'individual' | 'corporate' | 'institution' | 'nation'>('all'); + + const filtered = useMemo(() => { + if (filter === 'all') return scenarios; + return scenarios.filter((scenario) => scenario.model === filter); + }, [scenarios, filter]); + + return ( +
+
+

Saved scenarios

+
+ {(['all', 'individual', 'corporate', 'institution', 'nation'] as const).map((option) => ( + + ))} +
+
+
+ + + + + + + + + + + {filtered.length === 0 ? ( + + + + ) : ( + filtered.map((scenario) => ( + + + + + + + )) + )} + +
ScenarioStatusUpdatedActions
+ No scenarios yet. Start with the guided flow to create your first projection. +
+

{scenario.name}

+

{scenario.model.toUpperCase()} MODEL

+
{scenario.status.replace('-', ' ')} + {formatDistanceToNow(new Date(scenario.updatedAt), { addSuffix: true })} + +
+ { + setActiveScenario(scenario.id); + setFlowScenario(scenario.id); + markStep('btc', { status: 'in-progress' }); + }} + > + Open + + + +
+
+
+
+ ); +} diff --git a/src/components/layout/AuthenticatedShell.tsx b/src/components/layout/AuthenticatedShell.tsx new file mode 100644 index 0000000..df8d51e --- /dev/null +++ b/src/components/layout/AuthenticatedShell.tsx @@ -0,0 +1,72 @@ +'use client'; + +import Link from 'next/link'; +import { useEffect } from 'react'; +import { useRouter, usePathname } from 'next/navigation'; +import { Bitcoin, LogOut } from 'lucide-react'; +import { useAuthStore } from '@/src/state/authStore'; +import { ScenarioContextBar } from '@/src/components/layout/ScenarioContextBar'; + +export function AuthenticatedShell({ children }: { children: React.ReactNode }) { + const router = useRouter(); + const pathname = usePathname(); + const { user, logout } = useAuthStore(); + + useEffect(() => { + if (!user) { + router.replace('/onboarding'); + } + }, [user, router]); + + if (!user) { + return null; + } + + const navItems = [ + { href: '/home', label: 'Home' }, + { href: '/models/btc', label: 'BTC' }, + { href: '/models/macro', label: 'Macro' }, + { href: '/models/individual', label: 'Individual' }, + { href: '/models/corporate', label: 'Corporate' }, + { href: '/models/institution', label: 'Institution' }, + { href: '/models/nation', label: 'Nation-State' } + ]; + + return ( +
+
+
+ + + + + Bitcoin24 + + + +
+ +
+
{children}
+
+ ); +} diff --git a/src/components/layout/ScenarioContextBar.tsx b/src/components/layout/ScenarioContextBar.tsx new file mode 100644 index 0000000..49c9ada --- /dev/null +++ b/src/components/layout/ScenarioContextBar.tsx @@ -0,0 +1,52 @@ +'use client'; + +import { Clock, Edit, FolderOpen } from 'lucide-react'; +import { useScenarioStore } from '@/src/state/scenarioStore'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; +import { formatDistanceToNow } from 'date-fns'; +import Link from 'next/link'; + +export function ScenarioContextBar() { + const { scenarioId, completion } = useGuidedFlowStore(); + const scenario = useScenarioStore((state) => (scenarioId ? state.scenarios[scenarioId] : undefined)); + + if (!scenario) { + return null; + } + + const completionStatus = Object.entries(completion).map(([key, value]) => ( +
+ + {key.toUpperCase()} + {value.status} +
+ )); + + return ( +
+
+
+
+ Active scenario + + switch + +
+

{scenario.name}

+
+ + Updated {formatDistanceToNow(new Date(scenario.updatedAt), { addSuffix: true })} + + + {scenario.model.toUpperCase()} MODEL + +
+
+
{completionStatus}
+ +
+
+ ); +} diff --git a/src/components/models/BTCInputsPanel.tsx b/src/components/models/BTCInputsPanel.tsx new file mode 100644 index 0000000..7d09fbc --- /dev/null +++ b/src/components/models/BTCInputsPanel.tsx @@ -0,0 +1,166 @@ +'use client'; + +import { useEffect, useMemo } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useScenarioStore } from '@/src/state/scenarioStore'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; +import clsx from 'clsx'; + +const schema = z.object({ + currentPrice: z.number().min(1000).max(1000000), + arrStart: z.number().min(-0.5).max(5), + arrDecay: z.number().min(0).max(1), + steadyStateArr: z.number().min(0).max(1), + steadyStateYear: z.number().min(2025).max(2045) +}); + +type Values = z.infer; + +const presets: Record> = { + Bear: { + currentPrice: 45000, + arrStart: 0.25, + arrDecay: 0.08, + steadyStateArr: 0.05, + steadyStateYear: 2030 + }, + Base: { + currentPrice: 68000, + arrStart: 0.38, + arrDecay: 0.06, + steadyStateArr: 0.08, + steadyStateYear: 2032 + }, + Bull: { + currentPrice: 85000, + arrStart: 0.5, + arrDecay: 0.04, + steadyStateArr: 0.1, + steadyStateYear: 2035 + } +}; + +export function BTCInputsPanel() { + const { scenarioId } = useGuidedFlowStore(); + const scenario = useScenarioStore((state) => (scenarioId ? state.scenarios[scenarioId] : undefined)); + const updateBTCInputs = useScenarioStore((state) => state.updateBTCInputs); + const markStep = useGuidedFlowStore((state) => state.markStep); + + const form = useForm({ + resolver: zodResolver(schema), + mode: 'onChange', + values: scenario?.btcAssumptions ?? { + currentPrice: 68000, + arrStart: 0.38, + arrDecay: 0.06, + steadyStateArr: 0.08, + steadyStateYear: 2032 + } + }); + + useEffect(() => { + const subscription = form.watch((values, { name, type }) => { + if (!scenarioId || !form.formState.isValid) return; + updateBTCInputs(scenarioId, (draft) => { + Object.assign(draft, values); + }); + markStep('btc', { status: 'in-progress' }); + }); + return () => subscription.unsubscribe(); + }, [form, markStep, scenarioId, updateBTCInputs]); + + const activePreset = useMemo(() => { + if (!scenario) return null; + return Object.entries(presets).find(([, preset]) => { + return ( + Math.abs((preset.currentPrice ?? 0) - scenario.btcAssumptions.currentPrice) < 1000 && + Math.abs((preset.arrStart ?? 0) - scenario.btcAssumptions.arrStart) < 0.02 + ); + })?.[0]; + }, [scenario]); + + return ( +
+
+ {Object.keys(presets).map((preset) => ( + + ))} +
+
+ } + /> + } + /> + } + /> + } + /> + } + /> +
+ {!form.formState.isValid && ( +

Some values are outside recommended bounds. Adjust to continue.

+ )} +
+ ); +} + +interface NumberFieldProps { + label: string; + suffix?: string; + step?: number; + value: number; + onChange: (value: number | string) => void; +} + +function NumberField({ label, suffix, step = 1, value, onChange }: NumberFieldProps) { + return ( + + ); +} diff --git a/src/components/models/BTCResults.tsx b/src/components/models/BTCResults.tsx new file mode 100644 index 0000000..f064a45 --- /dev/null +++ b/src/components/models/BTCResults.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { useMemo } from 'react'; +import { useScenarioStore } from '@/src/state/scenarioStore'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; +import { formatCurrency, formatPercent } from '@/src/lib/calculations'; + +export function BTCResults() { + const { scenarioId } = useGuidedFlowStore(); + const scenario = useScenarioStore((state) => (scenarioId ? state.scenarios[scenarioId] : undefined)); + + const kpis = useMemo(() => { + if (!scenario) return []; + const terminal = scenario.btcOutputs[scenario.btcOutputs.length - 1]; + const marketCapShare = terminal.marketCap / (scenario.macroAssumptions.globalAssetBase * 1e12); + return [ + { label: 'Current price', value: formatCurrency(scenario.btcAssumptions.currentPrice) }, + { label: '2045 price', value: formatCurrency(terminal.price) }, + { label: '2045 market cap', value: formatCurrency(terminal.marketCap) }, + { label: 'Asset share', value: formatPercent(marketCapShare) } + ]; + }, [scenario]); + + if (!scenario) { + return null; + } + + return ( +
+
+ {kpis.map((kpi) => ( +
+

{kpi.label}

+

{kpi.value}

+
+ ))} +
+
+ + + + + + + + + + + {scenario.btcOutputs.map((row) => ( + + + + + + + ))} + +
YearARRPriceMarket cap
{row.year}{formatPercent(row.arr)}{formatCurrency(row.price)}{formatCurrency(row.marketCap)}
+
+
+ ); +} diff --git a/src/components/models/MacroPanel.tsx b/src/components/models/MacroPanel.tsx new file mode 100644 index 0000000..6cdb416 --- /dev/null +++ b/src/components/models/MacroPanel.tsx @@ -0,0 +1,157 @@ +'use client'; + +import { Controller, useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useScenarioStore } from '@/src/state/scenarioStore'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; +import clsx from 'clsx'; +import { useEffect } from 'react'; + +const schema = z.object({ + globalAssetBase: z.number().min(100).max(2000), + adoptionStart: z.number().min(0).max(1), + adoptionEnd: z.number().min(0).max(1), + inflationDrift: z.number().min(-0.02).max(0.15), + productivityBoost: z.number().min(-0.02).max(0.2) +}); + +type Values = z.infer; + +export function MacroPanel() { + const { scenarioId } = useGuidedFlowStore(); + const scenario = useScenarioStore((state) => (scenarioId ? state.scenarios[scenarioId] : undefined)); + const updateMacroInputs = useScenarioStore((state) => state.updateMacroInputs); + const markStep = useGuidedFlowStore((state) => state.markStep); + + const form = useForm({ + resolver: zodResolver(schema), + mode: 'onBlur', + values: + scenario?.macroAssumptions ?? { + globalAssetBase: 900, + adoptionStart: 0.03, + adoptionEnd: 0.18, + inflationDrift: 0.025, + productivityBoost: 0.02 + } + }); + + useEffect(() => { + const subscription = form.watch((values) => { + if (!scenarioId || !form.formState.isValid) return; + updateMacroInputs(scenarioId, (draft) => { + Object.assign(draft, values); + }); + markStep('macro', { status: 'in-progress' }); + }); + return () => subscription.unsubscribe(); + }, [form, markStep, scenarioId, updateMacroInputs]); + + if (!scenario) { + return

Create a scenario via onboarding to configure macro assumptions.

; + } + + return ( +
+

+ Macro assumptions cascade from BTC outcomes. Adjust adoption curves, inflation drift, and productivity boosts to see + downstream effects. +

+
+ } + /> + ( + field.onChange(Number(value) / 100)} + /> + )} + /> + ( + field.onChange(Number(value) / 100)} + /> + )} + /> + ( + field.onChange(Number(value) / 100)} + /> + )} + /> + ( + field.onChange(Number(value) / 100)} + /> + )} + /> +
+ {!form.formState.isValid && ( +
+ Adoption end must exceed start; values remain editable but warnings persist across the stepper. +
+ )} +
+ ); +} + +interface MacroFieldProps { + label: string; + suffix?: string; + step?: number; + value: number; + onChange: (value: number | string) => void; +} + +function MacroField({ label, suffix, step = 1, value, onChange }: MacroFieldProps) { + const isPercent = suffix === '%'; + return ( + + ); +} diff --git a/src/components/models/MacroResults.tsx b/src/components/models/MacroResults.tsx new file mode 100644 index 0000000..5284ffd --- /dev/null +++ b/src/components/models/MacroResults.tsx @@ -0,0 +1,43 @@ +'use client'; + +import { useMemo } from 'react'; +import { useScenarioStore } from '@/src/state/scenarioStore'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; +import { formatPercent } from '@/src/lib/calculations'; + +export function MacroResults() { + const { scenarioId } = useGuidedFlowStore(); + const scenario = useScenarioStore((state) => (scenarioId ? state.scenarios[scenarioId] : undefined)); + + const summaries = useMemo(() => { + if (!scenario) return []; + const latestLiquidity = scenario.macroOutputs.liquidity[scenario.macroOutputs.liquidity.length - 1]; + const adoption = scenario.macroOutputs.adoptionShare[scenario.macroOutputs.adoptionShare.length - 1]; + return [ + { label: '2045 liquidity support', value: `$${latestLiquidity.toLocaleString('en-US')}` }, + { label: '2045 adoption share', value: formatPercent(adoption) }, + { label: 'Years modelled', value: scenario.macroOutputs.gdp.length } + ]; + }, [scenario]); + + if (!scenario) return null; + + return ( +
+
+ {summaries.map((item) => ( +
+

{item.label}

+

{item.value}

+
+ ))} +
+
+

+ Adoption curves interpolate linearly between start and end assumptions. Liquidity estimates multiply BTC market cap by + adoption share, echoing workbook heuristics. Outputs stream to sector models for terminal value calculations. +

+
+
+ ); +} diff --git a/src/components/models/ModelDetail.tsx b/src/components/models/ModelDetail.tsx new file mode 100644 index 0000000..c524d51 --- /dev/null +++ b/src/components/models/ModelDetail.tsx @@ -0,0 +1,177 @@ +'use client'; + +import { Controller, useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useScenarioStore } from '@/src/state/scenarioStore'; +import { useGuidedFlowStore } from '@/src/state/guidedFlowStore'; +import { ModelKind } from '@/src/types/scenario'; +import { useEffect, useMemo } from 'react'; +import { formatCurrency } from '@/src/lib/calculations'; + +const schema = z.object({ + allocation: z.number().min(0).max(1), + treasuryShare: z.number().min(0).max(1), + cashflows: z.number().min(0), + leverage: z.number().min(0).max(2) +}); + +type Values = z.infer; + +const copy: Record = { + individual: { + title: 'Individual runway planning', + description: 'Track BTC holdings and cashflow coverage for personal accumulation strategies.' + }, + corporate: { + title: 'Corporate treasury strategy', + description: 'Balance treasury allocations, leverage, and coverage ratios for resilient balance sheets.' + }, + institution: { + title: 'Institutional portfolio design', + description: 'Model endowment, pension, or fund exposure with disciplined allocation guardrails.' + }, + nation: { + title: 'Nation-state reserve planning', + description: 'Evaluate sovereign reserve adoption, FX buffers, and productivity boosts.' + } +}; + +interface ModelDetailProps { + model: ModelKind; +} + +export function ModelDetail({ model }: ModelDetailProps) { + const { scenarioId } = useGuidedFlowStore(); + const scenario = useScenarioStore((state) => (scenarioId ? state.scenarios[scenarioId] : undefined)); + const updateModelInputs = useScenarioStore((state) => state.updateModelInputs); + const markStep = useGuidedFlowStore((state) => state.markStep); + + const form = useForm({ + resolver: zodResolver(schema), + mode: 'onBlur', + values: scenario?.modelInputs[model] ?? { + allocation: 0.1, + treasuryShare: 0.1, + cashflows: 1000000, + leverage: 0.2 + } + }); + + useEffect(() => { + const subscription = form.watch((values) => { + if (!scenarioId || !form.formState.isValid) return; + updateModelInputs(scenarioId, model, (draft) => { + Object.assign(draft, values); + }); + markStep('model', { status: 'in-progress' }); + }); + return () => subscription.unsubscribe(); + }, [form, markStep, model, scenarioId, updateModelInputs]); + + const outputs = useMemo(() => { + if (!scenario) return null; + const result = scenario.modelOutputs[model]; + const terminal = formatCurrency(result.terminalValue); + const latestCoverage = result.cashflowCoverage[result.cashflowCoverage.length - 1]?.toFixed(1) ?? '0.0'; + return { terminal, latestCoverage }; + }, [scenario, model]); + + if (!scenario) { + return null; + } + + return ( +
+
+

{copy[model].title}

+

{copy[model].description}

+
+
+ ( + field.onChange(Number(value) / 100)} + /> + )} + /> + ( + field.onChange(Number(value) / 100)} + /> + )} + /> + ( + + )} + /> + ( + + )} + /> +
+ {outputs && ( +
+
+

Terminal value

+

{outputs.terminal}

+
+
+

Coverage ratio

+

{outputs.latestCoverage}×

+
+
+ )} + {!form.formState.isValid && ( +

+ Double-check allocation, leverage, and treasury growth percentages. Critical errors prevent completion. +

+ )} +
+ ); +} + +interface ModelFieldProps { + label: string; + suffix?: string; + step?: number; + value: number; + onChange: (value: number | string) => void; +} + +function ModelField({ label, suffix, step = 1, value, onChange }: ModelFieldProps) { + return ( + + ); +} diff --git a/src/components/onboarding/AuthStep.tsx b/src/components/onboarding/AuthStep.tsx new file mode 100644 index 0000000..d994bf3 --- /dev/null +++ b/src/components/onboarding/AuthStep.tsx @@ -0,0 +1,183 @@ +'use client'; + +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Eye, EyeOff, Loader2 } from 'lucide-react'; +import clsx from 'clsx'; +import { useAuthStore } from '@/src/state/authStore'; +import toast from 'react-hot-toast'; + +const signupSchema = z + .object({ + email: z.string().email('Enter a valid email'), + username: z.string().min(3, 'Minimum 3 characters'), + password: z.string().min(8, 'Use at least 8 characters'), + confirmPassword: z.string() + }) + .refine((data) => data.password === data.confirmPassword, { + message: 'Passwords must match', + path: ['confirmPassword'] + }); + +const loginSchema = z.object({ + email: z.string().email('Enter a valid email'), + password: z.string().min(1, 'Password required') +}); + +interface AuthStepProps { + mode: 'signup' | 'login'; + onModeChange: (mode: 'signup' | 'login') => void; + onSuccess: () => void; +} + +type SignupValues = z.infer; +type LoginValues = z.infer; + +export function AuthStep({ mode, onModeChange, onSuccess }: AuthStepProps) { + const [showPassword, setShowPassword] = useState(false); + const signupForm = useForm({ resolver: zodResolver(signupSchema), mode: 'onChange' }); + const loginForm = useForm({ resolver: zodResolver(loginSchema), mode: 'onChange' }); + const login = useAuthStore((state) => state.login); + const signup = useAuthStore((state) => state.signup); + const [loading, setLoading] = useState(false); + + async function handleSignup(values: SignupValues) { + setLoading(true); + try { + await signup({ email: values.email, password: values.password, username: values.username }); + toast.success('Account created. Welcome aboard!'); + onSuccess(); + } catch (error) { + toast.error(error instanceof Error ? error.message : 'Unable to create account'); + } finally { + setLoading(false); + } + } + + async function handleLogin(values: LoginValues) { + setLoading(true); + try { + await login({ email: values.email, password: values.password }); + toast.success('Signed in successfully'); + onSuccess(); + } catch (error) { + toast.error(error instanceof Error ? error.message : 'Unable to sign in'); + } finally { + setLoading(false); + } + } + + return ( +
+
+ + +
+ {mode === 'signup' ? ( +
+ + + + + + + +
+ + +
+
+ + + + +
+ ) : ( +
+ + + + + + + +
+ )} +

+ By proceeding you agree to the Bitcoin24 modeling terms. Validation occurs inline and unsaved progress is preserved until completion. +

+
+ ); +} + +function FormField({ label, error, children }: { label: string; error?: string; children: React.ReactNode }) { + return ( + + ); +} diff --git a/src/components/onboarding/PriceStep.tsx b/src/components/onboarding/PriceStep.tsx new file mode 100644 index 0000000..dfc5b0e --- /dev/null +++ b/src/components/onboarding/PriceStep.tsx @@ -0,0 +1,143 @@ +'use client'; + +import { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { CalendarDays, Loader2, TrendingUp } from 'lucide-react'; +import { useLivePrice } from '@/src/hooks/useLivePrice'; +import { useOnboardingStore } from '@/src/state/onboardingStore'; +import { formatCurrency } from '@/src/lib/calculations'; + +const schema = z + .object({ + mode: z.union([z.literal('live'), z.literal('historical'), z.literal('custom')]), + customPrice: z.number().optional(), + historicalDate: z.string().optional() + }) + .refine((values) => { + if (values.mode === 'custom') { + return typeof values.customPrice === 'number' && values.customPrice > 0; + } + if (values.mode === 'historical') { + return Boolean(values.historicalDate); + } + return true; + }, 'Please complete your selection'); + +type Values = z.infer; + +interface PriceStepProps { + onConfirm: (selection: Values) => void; +} + +export function PriceStep({ onConfirm }: PriceStepProps) { + const { data, isFetching, refetch } = useLivePrice(); + const { priceSelection } = useOnboardingStore(); + const form = useForm({ + resolver: zodResolver(schema), + defaultValues: { + mode: priceSelection.mode, + customPrice: priceSelection.customPrice, + historicalDate: priceSelection.historicalDate + } + }); + + useEffect(() => { + if (data && !form.getValues('customPrice')) { + form.setValue('customPrice', Math.round(data.price)); + } + }, [data, form]); + + const values = form.watch(); + + return ( +
{ + onConfirm(values); + })} + > +
+

Select your starting price

+

+ BTC projections will anchor to this price. You can always revisit this choice from the home dashboard or guided flow. +

+
+
+ + + +
+
+ + +
+ {form.formState.errors.root &&

{form.formState.errors.root.message}

} +
+ ); +} diff --git a/src/components/onboarding/WelcomeStep.tsx b/src/components/onboarding/WelcomeStep.tsx new file mode 100644 index 0000000..0926cf1 --- /dev/null +++ b/src/components/onboarding/WelcomeStep.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { ArrowRight } from 'lucide-react'; + +interface WelcomeStepProps { + onContinue: () => void; + onLogin: () => void; +} + +export function WelcomeStep({ onContinue, onLogin }: WelcomeStepProps) { + return ( +
+
+

Welcome to Bitcoin24

+

+ In three steps you'll create your account, lock in a starting price, and enter the guided modeling experience. +

+
+
+ + +
+
+

• Persist scenarios locally with resilient auto-save and draft recovery.

+

• Guided flow keeps BTC, macro, and sector models synchronized.

+

• Live price integration ensures you model from today's market reality.

+

• Accessibility-first experience ready for keyboard and screen readers.

+
+
+ ); +} diff --git a/src/components/onboarding/WizardLayout.tsx b/src/components/onboarding/WizardLayout.tsx new file mode 100644 index 0000000..c250c5b --- /dev/null +++ b/src/components/onboarding/WizardLayout.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { motion, AnimatePresence } from 'framer-motion'; +import { ReactNode } from 'react'; +import Link from 'next/link'; + +const variants = { + enter: { opacity: 0, x: 60 }, + center: { opacity: 1, x: 0 }, + exit: { opacity: 0, x: -60 } +}; + +interface WizardLayoutProps { + step: number; + totalSteps: number; + title: string; + description: string; + children: ReactNode; +} + +export function WizardLayout({ step, totalSteps, title, description, children }: WizardLayoutProps) { + const progress = ((step + 1) / totalSteps) * 100; + return ( +
+
+
+
+
+
+

Onboarding

+

{title}

+

{description}

+
+
+

+ Step {step + 1} of {totalSteps} +

+
+ +
+
+
+
+
+ + + {children} + + +
+
+

What to expect

+
    +
  • ✅ Create your secure account
  • +
  • ✅ Choose a starting price (live, historical, or custom)
  • +
  • ✅ Jump straight into the guided modeling flow
  • +
+
+
+ Already exploring?{' '} + + Return to cover + +
+
+
+
+
+ ); +} diff --git a/src/hooks/useLivePrice.ts b/src/hooks/useLivePrice.ts new file mode 100644 index 0000000..bb8443f --- /dev/null +++ b/src/hooks/useLivePrice.ts @@ -0,0 +1,13 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; +import { fetchLivePrice } from '@/src/lib/pricing'; + +export function useLivePrice() { + return useQuery({ + queryKey: ['live-price'], + queryFn: () => fetchLivePrice(), + refetchInterval: 60_000, + staleTime: 30_000 + }); +} diff --git a/src/lib/calculations.ts b/src/lib/calculations.ts new file mode 100644 index 0000000..7003c62 --- /dev/null +++ b/src/lib/calculations.ts @@ -0,0 +1,147 @@ +import { BTCInputs, MacroInputs, ModelInputs, ModelKind, ScenarioDetail, ScenarioSummary, YearlyBTCRow, MacroOutputs, ModelOutputs } from '@/src/types/scenario'; +import { nanoid } from 'nanoid'; + +const START_YEAR = 2024; +const HORIZON = 21; + +export function createDefaultBTCInputs(): BTCInputs { + return { + currentPrice: 68000, + arrStart: 0.38, + arrDecay: 0.06, + steadyStateArr: 0.08, + steadyStateYear: 2032 + }; +} + +export function createDefaultMacroInputs(): MacroInputs { + return { + globalAssetBase: 900, // trillions + adoptionStart: 0.03, + adoptionEnd: 0.18, + inflationDrift: 0.025, + productivityBoost: 0.02 + }; +} + +export function createDefaultModelInputs(kind: ModelKind): ModelInputs { + switch (kind) { + case 'individual': + return { allocation: 0.15, treasuryShare: 0, cashflows: 120000, leverage: 0 }; + case 'corporate': + return { allocation: 0.05, treasuryShare: 0.12, cashflows: 60000000, leverage: 0.4 }; + case 'institution': + return { allocation: 0.08, treasuryShare: 0.2, cashflows: 450000000, leverage: 0.25 }; + case 'nation': + return { allocation: 0.05, treasuryShare: 0.35, cashflows: 1200000000, leverage: 0.1 }; + } +} + +export function generateBTCProjection(inputs: BTCInputs): YearlyBTCRow[] { + const rows: YearlyBTCRow[] = []; + let arr = inputs.arrStart; + let price = inputs.currentPrice; + for (let i = 0; i < HORIZON; i++) { + const year = START_YEAR + i; + if (year > inputs.steadyStateYear) { + arr = Math.max(inputs.steadyStateArr, arr - inputs.arrDecay * 0.25); + } else if (i > 0) { + arr = Math.max(inputs.steadyStateArr, arr - inputs.arrDecay); + } + price = price * (1 + arr); + const supply = 21000000 - Math.min(i * 350000, 21000000 * 0.1); + const marketCap = price * supply; + rows.push({ year, arr, price, marketCap }); + } + return rows; +} + +export function deriveMacroOutputs(inputs: MacroInputs, btcRows: YearlyBTCRow[]): MacroOutputs { + const gdp: number[] = []; + const adoptionShare: number[] = []; + const liquidity: number[] = []; + const baseGDP = inputs.globalAssetBase; + const adoptionRange = inputs.adoptionEnd - inputs.adoptionStart; + btcRows.forEach((row, index) => { + const yearsFromStart = index; + const gdpValue = baseGDP * Math.pow(1 + inputs.productivityBoost, yearsFromStart) * (1 + inputs.inflationDrift); + const adoption = inputs.adoptionStart + (adoptionRange * index) / (btcRows.length - 1); + const liquidityValue = row.marketCap * adoption; + gdp.push(gdpValue); + adoptionShare.push(adoption); + liquidity.push(liquidityValue); + }); + return { gdp, adoptionShare, liquidity }; +} + +export function deriveModelOutputs(kind: ModelKind, btcRows: YearlyBTCRow[], inputs: ModelInputs): ModelOutputs { + const holdings: number[] = []; + const coverage: number[] = []; + let btcHeld = inputs.allocation * inputs.cashflows; + btcRows.forEach((row) => { + btcHeld = btcHeld * (1 + inputs.treasuryShare) + inputs.cashflows * inputs.allocation; + const coverageRatio = (btcHeld * row.price) / (inputs.cashflows * (1 + inputs.leverage)); + holdings.push(btcHeld); + coverage.push(coverageRatio); + }); + const terminalValue = btcHeld * btcRows[btcRows.length - 1]?.price; + return { terminalValue, btcHoldings: holdings, cashflowCoverage: coverage }; +} + +export function createScenarioSummary(name: string, model: ModelKind): ScenarioSummary { + const btcAssumptions = createDefaultBTCInputs(); + const macroAssumptions = createDefaultMacroInputs(); + const now = new Date().toISOString(); + return { + id: nanoid(), + name, + createdAt: now, + updatedAt: now, + status: 'draft', + model, + btcAssumptions, + macroAssumptions + }; +} + +export function hydrateScenarioDetail(summary: ScenarioSummary): ScenarioDetail { + const btcOutputs = generateBTCProjection(summary.btcAssumptions); + const macroOutputs = deriveMacroOutputs(summary.macroAssumptions, btcOutputs); + const modelInputs: Record = { + individual: createDefaultModelInputs('individual'), + corporate: createDefaultModelInputs('corporate'), + institution: createDefaultModelInputs('institution'), + nation: createDefaultModelInputs('nation') + }; + const modelOutputs: Record = { + individual: deriveModelOutputs('individual', btcOutputs, modelInputs.individual), + corporate: deriveModelOutputs('corporate', btcOutputs, modelInputs.corporate), + institution: deriveModelOutputs('institution', btcOutputs, modelInputs.institution), + nation: deriveModelOutputs('nation', btcOutputs, modelInputs.nation) + }; + return { + ...summary, + btcOutputs, + macroOutputs, + modelOutputs, + modelInputs + }; +} + +export function formatCurrency(value: number, options: Intl.NumberFormatOptions = {}): string { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + maximumFractionDigits: 0, + notation: 'compact', + ...options + }).format(value); +} + +export function formatPercent(value: number): string { + return new Intl.NumberFormat('en-US', { + style: 'percent', + minimumFractionDigits: 1, + maximumFractionDigits: 1 + }).format(value); +} diff --git a/src/lib/pricing.ts b/src/lib/pricing.ts new file mode 100644 index 0000000..f5f3976 --- /dev/null +++ b/src/lib/pricing.ts @@ -0,0 +1,34 @@ +const COINGECKO_URL = 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd'; + +export interface LivePriceResult { + price: number; + source: string; + timestamp: string; +} + +export async function fetchLivePrice(signal?: AbortSignal): Promise { + try { + const response = await fetch(COINGECKO_URL, { signal, next: { revalidate: 60 } }); + if (!response.ok) { + throw new Error('Failed to fetch live price'); + } + const data = await response.json(); + const price = data.bitcoin?.usd; + if (!price) { + throw new Error('Malformed live price response'); + } + return { + price, + source: 'CoinGecko', + timestamp: new Date().toISOString() + }; + } catch (error) { + console.warn('Falling back to cached live price', error); + const fallback = 68000; + return { + price: fallback, + source: 'Cached fallback', + timestamp: new Date().toISOString() + }; + } +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..59e2a19 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,42 @@ +import 'reflect-metadata'; +import { ValidationPipe } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { NestFactory } from '@nestjs/core'; +import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; +import cookie from '@fastify/cookie'; +import { AppModule } from './app/app.module'; +import { PrismaService } from './prisma/prisma.service'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule, new FastifyAdapter()); + + const configService = app.get(ConfigService); + const cookieSecret = configService.get('COOKIE_SECRET', 'cookie-secret'); + await app.register(cookie, { + secret: cookieSecret, + }); + + const corsOrigin = configService.get('CORS_ORIGIN'); + const allowedOrigins = corsOrigin ? corsOrigin.split(',').map((origin) => origin.trim()) : true; + app.enableCors({ + origin: allowedOrigins, + credentials: true, + }); + + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + transform: true, + transformOptions: { enableImplicitConversion: true }, + }), + ); + + const prismaService = app.get(PrismaService); + await prismaService.enableShutdownHooks(app); + + const port = Number(configService.get('PORT') ?? 3000); + const host = configService.get('HOST') ?? '0.0.0.0'; + await app.listen(port, host); +} + +bootstrap(); diff --git a/src/metrics.ts b/src/metrics.ts new file mode 100644 index 0000000..d8fed75 --- /dev/null +++ b/src/metrics.ts @@ -0,0 +1,19 @@ +export type MetricKey = "lcp" | "accessibility" | "apiLatency"; + +export interface MetricBudget { + key: MetricKey; + threshold: number; +} + +export interface MetricResult { + key: MetricKey; + value: number; +} + +export const withinThreshold = (budget: MetricBudget, result: MetricResult): boolean => { + if (budget.key !== result.key) { + throw new Error(`Metric key mismatch: expected ${budget.key}, received ${result.key}`); + } + + return result.value <= budget.threshold; +}; diff --git a/src/model/dto/execute-model.dto.ts b/src/model/dto/execute-model.dto.ts new file mode 100644 index 0000000..beec1e0 --- /dev/null +++ b/src/model/dto/execute-model.dto.ts @@ -0,0 +1,10 @@ +import { Field, InputType, Int } from '@nestjs/graphql'; +import { IsInt, Min } from 'class-validator'; + +@InputType() +export class ExecuteModelDto { + @Field(() => Int) + @IsInt() + @Min(1) + scenarioId!: number; +} diff --git a/src/model/model.controller.ts b/src/model/model.controller.ts new file mode 100644 index 0000000..ec85700 --- /dev/null +++ b/src/model/model.controller.ts @@ -0,0 +1,16 @@ +import { Body, Controller, Post, UseGuards } from '@nestjs/common'; +import { CurrentUser } from '../common/decorators/current-user.decorator'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { ExecuteModelDto } from './dto/execute-model.dto'; +import { ModelService } from './model.service'; + +@Controller('model') +@UseGuards(JwtAuthGuard) +export class ModelController { + constructor(private readonly modelService: ModelService) {} + + @Post('execute') + execute(@CurrentUser() user: any, @Body() dto: ExecuteModelDto) { + return this.modelService.executeScenario(user.id, dto.scenarioId); + } +} diff --git a/src/model/model.module.ts b/src/model/model.module.ts new file mode 100644 index 0000000..256cd20 --- /dev/null +++ b/src/model/model.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { PricingModule } from '../pricing/pricing.module'; +import { ScenarioModule } from '../scenario/scenario.module'; +import { ModelController } from './model.controller'; +import { ModelResolver } from './model.resolver'; +import { ModelService } from './model.service'; + +@Module({ + imports: [ScenarioModule, PricingModule], + controllers: [ModelController], + providers: [ModelService, ModelResolver], +}) +export class ModelModule {} diff --git a/src/model/model.resolver.ts b/src/model/model.resolver.ts new file mode 100644 index 0000000..02ac7f3 --- /dev/null +++ b/src/model/model.resolver.ts @@ -0,0 +1,18 @@ +import { Args, Mutation, Resolver } from '@nestjs/graphql'; +import { UseGuards } from '@nestjs/common'; +import { CurrentUser } from '../common/decorators/current-user.decorator'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { ExecuteModelDto } from './dto/execute-model.dto'; +import { ModelService } from './model.service'; +import { ModelExecutionResult } from './models/model-execution-result.model'; + +@Resolver(() => ModelExecutionResult) +@UseGuards(JwtAuthGuard) +export class ModelResolver { + constructor(private readonly modelService: ModelService) {} + + @Mutation(() => ModelExecutionResult) + executeScenario(@CurrentUser() user: any, @Args('input') input: ExecuteModelDto) { + return this.modelService.executeScenario(user.id, input.scenarioId); + } +} diff --git a/src/model/model.service.ts b/src/model/model.service.ts new file mode 100644 index 0000000..465f702 --- /dev/null +++ b/src/model/model.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@nestjs/common'; +import { PricingService } from '../pricing/pricing.service'; +import { ScenarioService } from '../scenario/scenario.service'; + +@Injectable() +export class ModelService { + constructor( + private readonly scenarioService: ScenarioService, + private readonly pricingService: PricingService, + ) {} + + async executeScenario(ownerId: number, scenarioId: number) { + const scenario = await this.scenarioService.findOne(ownerId, scenarioId); + const latestPrice = await this.pricingService.getLatestPrice(); + + const targetPrice = Number((scenario.parameters as any)?.targetPrice ?? 0); + const latestPriceValue = latestPrice ? Number(latestPrice.priceUsd) : null; + const delta = targetPrice && latestPriceValue ? targetPrice - latestPriceValue : null; + + return { + scenario, + latestPrice, + summary: { + targetPrice: targetPrice || null, + latestPrice: latestPriceValue, + differenceToTarget: delta, + generatedAt: new Date().toISOString(), + }, + }; + } +} diff --git a/src/model/models/model-execution-result.model.ts b/src/model/models/model-execution-result.model.ts new file mode 100644 index 0000000..2bbff93 --- /dev/null +++ b/src/model/models/model-execution-result.model.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; +import { GraphQLJSONObject } from 'graphql-type-json'; +import { BtcPriceModel } from '../../pricing/models/btc-price.model'; +import { ScenarioModel } from '../../scenario/models/scenario.model'; + +@ObjectType() +export class ModelExecutionResult { + @Field(() => ScenarioModel) + scenario!: ScenarioModel; + + @Field(() => BtcPriceModel, { nullable: true }) + latestPrice?: BtcPriceModel | null; + + @Field(() => GraphQLJSONObject) + summary!: Record; +} diff --git a/src/pricing/models/btc-price.model.ts b/src/pricing/models/btc-price.model.ts new file mode 100644 index 0000000..2bbca7b --- /dev/null +++ b/src/pricing/models/btc-price.model.ts @@ -0,0 +1,19 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; + +@ObjectType() +export class BtcPriceModel { + @Field(() => ID) + id!: number; + + @Field() + priceUsd!: string; + + @Field() + source!: string; + + @Field() + asOf!: Date; + + @Field() + createdAt!: Date; +} diff --git a/src/pricing/price-sync.service.ts b/src/pricing/price-sync.service.ts new file mode 100644 index 0000000..d9db909 --- /dev/null +++ b/src/pricing/price-sync.service.ts @@ -0,0 +1,24 @@ +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { Cron, CronExpression } from '@nestjs/schedule'; +import { PricingService } from './pricing.service'; + +@Injectable() +export class PriceSyncService implements OnModuleInit { + private readonly logger = new Logger(PriceSyncService.name); + + constructor(private readonly pricingService: PricingService) {} + + async onModuleInit() { + await this.syncPrices('startup'); + } + + @Cron(CronExpression.EVERY_10_MINUTES) + async handleCron() { + await this.syncPrices('scheduled'); + } + + private async syncPrices(trigger: 'startup' | 'scheduled') { + this.logger.debug(`Triggering BTC price sync (${trigger})`); + await this.pricingService.syncLatestPrice(); + } +} diff --git a/src/pricing/pricing.controller.ts b/src/pricing/pricing.controller.ts new file mode 100644 index 0000000..e1bcab0 --- /dev/null +++ b/src/pricing/pricing.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { PricingService } from './pricing.service'; + +@Controller('pricing') +@UseGuards(JwtAuthGuard) +export class PricingController { + constructor(private readonly pricingService: PricingService) {} + + @Get('latest') + getLatestPrice() { + return this.pricingService.getLatestPrice(); + } +} diff --git a/src/pricing/pricing.module.ts b/src/pricing/pricing.module.ts new file mode 100644 index 0000000..2aa519b --- /dev/null +++ b/src/pricing/pricing.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { PrismaModule } from '../prisma/prisma.module'; +import { PriceSyncService } from './price-sync.service'; +import { PricingController } from './pricing.controller'; +import { PricingResolver } from './pricing.resolver'; +import { PricingService } from './pricing.service'; + +@Module({ + imports: [ConfigModule, PrismaModule], + controllers: [PricingController], + providers: [PricingService, PricingResolver, PriceSyncService], + exports: [PricingService], +}) +export class PricingModule {} diff --git a/src/pricing/pricing.resolver.ts b/src/pricing/pricing.resolver.ts new file mode 100644 index 0000000..b610302 --- /dev/null +++ b/src/pricing/pricing.resolver.ts @@ -0,0 +1,16 @@ +import { Query, Resolver } from '@nestjs/graphql'; +import { UseGuards } from '@nestjs/common'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { BtcPriceModel } from './models/btc-price.model'; +import { PricingService } from './pricing.service'; + +@Resolver(() => BtcPriceModel) +@UseGuards(JwtAuthGuard) +export class PricingResolver { + constructor(private readonly pricingService: PricingService) {} + + @Query(() => BtcPriceModel, { nullable: true }) + latestBtcPrice() { + return this.pricingService.getLatestPrice(); + } +} diff --git a/src/pricing/pricing.service.ts b/src/pricing/pricing.service.ts new file mode 100644 index 0000000..b51ea71 --- /dev/null +++ b/src/pricing/pricing.service.ts @@ -0,0 +1,73 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { PrismaService } from '../prisma/prisma.service'; +import fetch from 'node-fetch'; + +interface ExternalPriceResponse { + priceUsd: number; + asOf: string; + source: string; +} + +@Injectable() +export class PricingService { + private readonly logger = new Logger(PricingService.name); + + constructor(private readonly prisma: PrismaService, private readonly configService: ConfigService) {} + + async getLatestPrice() { + return this.prisma.btcPrice.findFirst({ orderBy: { asOf: 'desc' } }); + } + + async syncLatestPrice(): Promise { + const externalPrice = await this.fetchExternalPrice(); + if (!externalPrice) { + this.logger.warn('No price returned from external provider'); + return; + } + + await this.prisma.btcPrice.upsert({ + where: { asOf: new Date(externalPrice.asOf) }, + update: { + priceUsd: externalPrice.priceUsd.toString(), + source: externalPrice.source, + }, + create: { + priceUsd: externalPrice.priceUsd.toString(), + source: externalPrice.source, + asOf: new Date(externalPrice.asOf), + }, + }); + } + + private async fetchExternalPrice(): Promise { + const endpoint = this.configService.get('BTC_PRICE_API', 'https://api.coindesk.com/v1/bpi/currentprice/BTC.json'); + + try { + const response = await fetch(endpoint); + if (!response.ok) { + this.logger.error(`Failed to fetch BTC price: ${response.status}`); + return null; + } + const data = (await response.json()) as any; + + const price = Number(data?.bpi?.USD?.rate_float ?? data?.priceUsd ?? data?.price); + if (!price || Number.isNaN(price)) { + this.logger.error('Unable to parse price from response'); + return null; + } + + const timestamp = data?.time?.updatedISO ?? data?.asOf ?? new Date().toISOString(); + const source = data?.chartName ?? data?.source ?? 'coindesk'; + + return { + priceUsd: price, + asOf: timestamp, + source, + }; + } catch (error) { + this.logger.error('Error fetching BTC price', error as Error); + return null; + } + } +} diff --git a/src/prisma/prisma.module.ts b/src/prisma/prisma.module.ts new file mode 100644 index 0000000..7207426 --- /dev/null +++ b/src/prisma/prisma.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common'; +import { PrismaService } from './prisma.service'; + +@Global() +@Module({ + providers: [PrismaService], + exports: [PrismaService], +}) +export class PrismaModule {} diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts new file mode 100644 index 0000000..5583f9e --- /dev/null +++ b/src/prisma/prisma.service.ts @@ -0,0 +1,30 @@ +import { INestApplication, Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + constructor(private readonly configService: ConfigService) { + super({ + datasources: { + db: { + url: configService.get('DATABASE_URL'), + }, + }, + }); + } + + async onModuleInit(): Promise { + await this.$connect(); + } + + async onModuleDestroy(): Promise { + await this.$disconnect(); + } + + async enableShutdownHooks(app: INestApplication): Promise { + this.$on('beforeExit', async () => { + await app.close(); + }); + } +} diff --git a/src/providers/AppProviders.tsx b/src/providers/AppProviders.tsx new file mode 100644 index 0000000..069391b --- /dev/null +++ b/src/providers/AppProviders.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactNode, useState } from 'react'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { Toaster } from 'react-hot-toast'; + +const isBrowser = typeof window !== 'undefined'; + +export function AppProviders({ children }: { children: ReactNode }) { + const [client] = useState(() => new QueryClient()); + + return ( + + {children} + {isBrowser && } + + + ); +} diff --git a/src/scenario/dto/create-scenario.dto.ts b/src/scenario/dto/create-scenario.dto.ts new file mode 100644 index 0000000..0980b8a --- /dev/null +++ b/src/scenario/dto/create-scenario.dto.ts @@ -0,0 +1,20 @@ +import { Field, InputType } from '@nestjs/graphql'; +import { GraphQLJSONObject } from 'graphql-type-json'; +import { IsNotEmpty, IsObject, IsOptional, IsString } from 'class-validator'; + +@InputType() +export class CreateScenarioDto { + @Field() + @IsString() + @IsNotEmpty() + name!: string; + + @Field({ nullable: true }) + @IsOptional() + @IsString() + description?: string; + + @Field(() => GraphQLJSONObject) + @IsObject() + parameters!: Record; +} diff --git a/src/scenario/dto/update-scenario.dto.ts b/src/scenario/dto/update-scenario.dto.ts new file mode 100644 index 0000000..cfc7584 --- /dev/null +++ b/src/scenario/dto/update-scenario.dto.ts @@ -0,0 +1,22 @@ +import { Field, InputType, PartialType } from '@nestjs/graphql'; +import { GraphQLJSONObject } from 'graphql-type-json'; +import { IsObject, IsOptional, IsString } from 'class-validator'; +import { CreateScenarioDto } from './create-scenario.dto'; + +@InputType() +export class UpdateScenarioDto extends PartialType(CreateScenarioDto) { + @Field({ nullable: true }) + @IsOptional() + @IsString() + name?: string; + + @Field({ nullable: true }) + @IsOptional() + @IsString() + description?: string; + + @Field(() => GraphQLJSONObject, { nullable: true }) + @IsOptional() + @IsObject() + parameters?: Record; +} diff --git a/src/scenario/models/scenario.model.ts b/src/scenario/models/scenario.model.ts new file mode 100644 index 0000000..1f8eec1 --- /dev/null +++ b/src/scenario/models/scenario.model.ts @@ -0,0 +1,30 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; +import { GraphQLJSONObject } from 'graphql-type-json'; +import { UserModel } from '../../auth/models/user.model'; + +@ObjectType() +export class ScenarioModel { + @Field(() => ID) + id!: number; + + @Field() + name!: string; + + @Field({ nullable: true }) + description?: string; + + @Field(() => GraphQLJSONObject) + parameters!: Record; + + @Field() + ownerId!: number; + + @Field(() => UserModel) + owner!: UserModel; + + @Field() + createdAt!: Date; + + @Field() + updatedAt!: Date; +} diff --git a/src/scenario/scenario.controller.ts b/src/scenario/scenario.controller.ts new file mode 100644 index 0000000..4fa66fd --- /dev/null +++ b/src/scenario/scenario.controller.ts @@ -0,0 +1,37 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from '@nestjs/common'; +import { CurrentUser } from '../common/decorators/current-user.decorator'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { CreateScenarioDto } from './dto/create-scenario.dto'; +import { UpdateScenarioDto } from './dto/update-scenario.dto'; +import { ScenarioService } from './scenario.service'; + +@Controller('scenarios') +@UseGuards(JwtAuthGuard) +export class ScenarioController { + constructor(private readonly scenarioService: ScenarioService) {} + + @Post() + create(@CurrentUser() user: any, @Body() dto: CreateScenarioDto) { + return this.scenarioService.create(user.id, dto); + } + + @Get() + findAll(@CurrentUser() user: any) { + return this.scenarioService.findAll(user.id); + } + + @Get(':id') + findOne(@CurrentUser() user: any, @Param('id', ParseIntPipe) id: number) { + return this.scenarioService.findOne(user.id, id); + } + + @Patch(':id') + update(@CurrentUser() user: any, @Param('id', ParseIntPipe) id: number, @Body() dto: UpdateScenarioDto) { + return this.scenarioService.update(user.id, id, dto); + } + + @Delete(':id') + remove(@CurrentUser() user: any, @Param('id', ParseIntPipe) id: number) { + return this.scenarioService.remove(user.id, id); + } +} diff --git a/src/scenario/scenario.module.ts b/src/scenario/scenario.module.ts new file mode 100644 index 0000000..bdced4d --- /dev/null +++ b/src/scenario/scenario.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { PrismaModule } from '../prisma/prisma.module'; +import { ScenarioController } from './scenario.controller'; +import { ScenarioResolver } from './scenario.resolver'; +import { ScenarioService } from './scenario.service'; + +@Module({ + imports: [PrismaModule], + controllers: [ScenarioController], + providers: [ScenarioService, ScenarioResolver], + exports: [ScenarioService], +}) +export class ScenarioModule {} diff --git a/src/scenario/scenario.resolver.ts b/src/scenario/scenario.resolver.ts new file mode 100644 index 0000000..40321aa --- /dev/null +++ b/src/scenario/scenario.resolver.ts @@ -0,0 +1,39 @@ +import { Args, Int, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { UseGuards } from '@nestjs/common'; +import { CurrentUser } from '../common/decorators/current-user.decorator'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { CreateScenarioDto } from './dto/create-scenario.dto'; +import { UpdateScenarioDto } from './dto/update-scenario.dto'; +import { ScenarioModel } from './models/scenario.model'; +import { ScenarioService } from './scenario.service'; + +@Resolver(() => ScenarioModel) +@UseGuards(JwtAuthGuard) +export class ScenarioResolver { + constructor(private readonly scenarioService: ScenarioService) {} + + @Mutation(() => ScenarioModel) + createScenario(@CurrentUser() user: any, @Args('input') input: CreateScenarioDto) { + return this.scenarioService.create(user.id, input); + } + + @Query(() => [ScenarioModel]) + scenarios(@CurrentUser() user: any) { + return this.scenarioService.findAll(user.id); + } + + @Query(() => ScenarioModel) + scenario(@CurrentUser() user: any, @Args('id', { type: () => Int }) id: number) { + return this.scenarioService.findOne(user.id, id); + } + + @Mutation(() => ScenarioModel) + updateScenario(@CurrentUser() user: any, @Args('id', { type: () => Int }) id: number, @Args('input') input: UpdateScenarioDto) { + return this.scenarioService.update(user.id, id, input); + } + + @Mutation(() => Boolean) + deleteScenario(@CurrentUser() user: any, @Args('id', { type: () => Int }) id: number) { + return this.scenarioService.remove(user.id, id); + } +} diff --git a/src/scenario/scenario.service.ts b/src/scenario/scenario.service.ts new file mode 100644 index 0000000..f7c647f --- /dev/null +++ b/src/scenario/scenario.service.ts @@ -0,0 +1,67 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateScenarioDto } from './dto/create-scenario.dto'; +import { UpdateScenarioDto } from './dto/update-scenario.dto'; + +const ownerSelect = { + id: true, + email: true, + name: true, + createdAt: true, + updatedAt: true, +}; + +@Injectable() +export class ScenarioService { + constructor(private readonly prisma: PrismaService) {} + + create(ownerId: number, dto: CreateScenarioDto) { + return this.prisma.scenario.create({ + data: { + name: dto.name, + description: dto.description, + parameters: dto.parameters, + ownerId, + }, + include: { owner: { select: ownerSelect } }, + }); + } + + findAll(ownerId: number) { + return this.prisma.scenario.findMany({ + where: { ownerId }, + orderBy: { createdAt: 'desc' }, + include: { owner: { select: ownerSelect } }, + }); + } + + async findOne(ownerId: number, id: number) { + const scenario = await this.prisma.scenario.findFirst({ + where: { id, ownerId }, + include: { owner: { select: ownerSelect } }, + }); + if (!scenario) { + throw new NotFoundException('Scenario not found'); + } + return scenario; + } + + async update(ownerId: number, id: number, dto: UpdateScenarioDto) { + await this.findOne(ownerId, id); + return this.prisma.scenario.update({ + where: { id }, + data: { + name: dto.name, + description: dto.description, + parameters: dto.parameters, + }, + include: { owner: { select: ownerSelect } }, + }); + } + + async remove(ownerId: number, id: number) { + await this.findOne(ownerId, id); + await this.prisma.scenario.delete({ where: { id } }); + return true; + } +} diff --git a/src/state/authStore.ts b/src/state/authStore.ts new file mode 100644 index 0000000..0237e15 --- /dev/null +++ b/src/state/authStore.ts @@ -0,0 +1,92 @@ +'use client'; + +import { create } from 'zustand'; +import { persist, createJSONStorage } from 'zustand/middleware'; +import { nanoid } from 'nanoid'; + +export interface UserProfile { + id: string; + email: string; + username: string; + createdAt: string; +} + +interface AuthState { + user: UserProfile | null; + onboardingComplete: boolean; + login: (payload: { email: string; password: string }) => Promise; + signup: (payload: { email: string; password: string; username: string }) => Promise; + logout: () => void; + markOnboardingComplete: () => void; +} + +interface StoredUser extends UserProfile { + password: string; +} + +const USERS_KEY = 'bitcoin-model-users'; + +const storage = createJSONStorage(() => localStorage); + +function loadUsers(): StoredUser[] { + if (typeof window === 'undefined') return []; + const raw = localStorage.getItem(USERS_KEY); + if (!raw) return []; + try { + return JSON.parse(raw); + } catch (error) { + console.error('Failed to parse users', error); + return []; + } +} + +function saveUsers(users: StoredUser[]) { + if (typeof window === 'undefined') return; + localStorage.setItem(USERS_KEY, JSON.stringify(users)); +} + +export const useAuthStore = create()( + persist( + (set, get) => ({ + user: null, + onboardingComplete: false, + async login({ email, password }) { + await new Promise((resolve) => setTimeout(resolve, 500)); + const users = loadUsers(); + const existing = users.find((item) => item.email === email && item.password === password); + if (!existing) { + throw new Error('Invalid credentials'); + } + set({ user: existing, onboardingComplete: true }); + }, + async signup({ email, password, username }) { + await new Promise((resolve) => setTimeout(resolve, 500)); + const users = loadUsers(); + if (users.some((item) => item.email === email)) { + throw new Error('Account already exists'); + } + const user: StoredUser = { + id: nanoid(), + email, + username, + createdAt: new Date().toISOString(), + password + }; + users.push(user); + saveUsers(users); + set({ user, onboardingComplete: true }); + }, + logout() { + set({ user: null, onboardingComplete: false }); + }, + markOnboardingComplete() { + set({ onboardingComplete: true }); + } + }), + { + name: 'auth-store', + storage, + partialize: (state) => ({ user: state.user, onboardingComplete: state.onboardingComplete }) + } + ) +); diff --git a/src/state/guidedFlowStore.ts b/src/state/guidedFlowStore.ts new file mode 100644 index 0000000..80b3fdf --- /dev/null +++ b/src/state/guidedFlowStore.ts @@ -0,0 +1,59 @@ +'use client'; + +import { create } from 'zustand'; +import { persist, createJSONStorage } from 'zustand/middleware'; +import { ScenarioStatus } from '@/src/types/scenario'; + +export type FlowStep = 'btc' | 'macro' | 'model'; + +interface StepState { + status: ScenarioStatus | 'not-started' | 'attention'; + validationMessage?: string; +} + +interface GuidedFlowState { + currentStep: FlowStep; + completion: Record; + scenarioId: string | null; + setScenario: (id: string) => void; + setStep: (step: FlowStep) => void; + markStep: (step: FlowStep, state: StepState) => void; + reset: () => void; +} + +const defaultCompletion = (): Record => ({ + btc: { status: 'not-started' }, + macro: { status: 'not-started' }, + model: { status: 'not-started' } +}); + +export const useGuidedFlowStore = create()( + persist( + (set) => ({ + currentStep: 'btc', + completion: defaultCompletion(), + scenarioId: null, + setScenario: (id) => + set({ + scenarioId: id, + completion: defaultCompletion(), + currentStep: 'btc' + }), + setStep: (step) => set({ currentStep: step }), + markStep: (step, state) => + set((current) => ({ + completion: { ...current.completion, [step]: state } + })), + reset: () => + set({ + currentStep: 'btc', + completion: defaultCompletion(), + scenarioId: null + }) + }), + { + name: 'guided-flow-store', + storage: createJSONStorage(() => localStorage) + } + ) +); diff --git a/src/state/onboardingStore.ts b/src/state/onboardingStore.ts new file mode 100644 index 0000000..5215042 --- /dev/null +++ b/src/state/onboardingStore.ts @@ -0,0 +1,50 @@ +'use client'; + +import { create } from 'zustand'; +import { persist, createJSONStorage } from 'zustand/middleware'; + +export type PriceSelectionMode = 'live' | 'historical' | 'custom'; + +export interface OnboardingState { + step: number; + mode: 'signup' | 'login'; + priceSelection: { + mode: PriceSelectionMode; + customPrice?: number; + historicalDate?: string; + }; + completed: boolean; + setStep: (step: number) => void; + setMode: (mode: 'signup' | 'login') => void; + setPriceSelection: (selection: OnboardingState['priceSelection']) => void; + complete: () => void; + reset: () => void; +} + +export const useOnboardingStore = create()( + persist( + (set) => ({ + step: 0, + mode: 'signup', + priceSelection: { + mode: 'live' + }, + completed: false, + setStep: (step) => set({ step }), + setMode: (mode) => set({ mode }), + setPriceSelection: (priceSelection) => set({ priceSelection }), + complete: () => set({ completed: true }), + reset: () => + set({ + step: 0, + mode: 'signup', + priceSelection: { mode: 'live' }, + completed: false + }) + }), + { + name: 'onboarding-store', + storage: createJSONStorage(() => sessionStorage) + } + ) +); diff --git a/src/state/scenarioStore.ts b/src/state/scenarioStore.ts new file mode 100644 index 0000000..b15b873 --- /dev/null +++ b/src/state/scenarioStore.ts @@ -0,0 +1,146 @@ +'use client'; + +import { create } from 'zustand'; +import { persist, createJSONStorage } from 'zustand/middleware'; +import { + ScenarioDetail, + ScenarioSummary, + ModelKind, + ScenarioStatus, + BTCInputs, + MacroInputs, + ModelInputs +} from '@/src/types/scenario'; +import { + createScenarioSummary, + hydrateScenarioDetail, + generateBTCProjection, + deriveMacroOutputs, + deriveModelOutputs +} from '@/src/lib/calculations'; +import { produce } from 'immer'; +import { nanoid } from 'nanoid'; + +interface ScenarioState { + activeScenarioId: string | null; + scenarios: Record; + createScenario: (name: string, model: ModelKind) => ScenarioDetail; + loadScenario: (id: string) => ScenarioDetail | undefined; + setActiveScenario: (id: string) => void; + updateBTCInputs: (id: string, updater: (draft: BTCInputs) => void) => void; + updateMacroInputs: (id: string, updater: (draft: MacroInputs) => void) => void; + updateModelInputs: (id: string, model: ModelKind, updater: (draft: ModelInputs) => void) => void; + markStatus: (id: string, status: ScenarioStatus) => void; + duplicateScenario: (id: string) => ScenarioDetail | undefined; + deleteScenario: (id: string) => void; +} + +function detailFromSummary(summary: ScenarioSummary): ScenarioDetail { + return hydrateScenarioDetail(summary); +} + +export const useScenarioStore = create()( + persist( + (set, get) => ({ + activeScenarioId: null, + scenarios: {}, + createScenario: (name, model) => { + const summary = createScenarioSummary(name, model); + const detail = detailFromSummary(summary); + set((state) => ({ + scenarios: { ...state.scenarios, [detail.id]: detail }, + activeScenarioId: detail.id + })); + return detail; + }, + loadScenario: (id) => get().scenarios[id], + setActiveScenario: (id) => set({ activeScenarioId: id }), + updateBTCInputs: (id, updater) => + set((state) => { + const scenario = state.scenarios[id]; + if (!scenario) return state; + const nextScenario = produce(scenario, (draft) => { + updater(draft.btcAssumptions); + draft.btcOutputs = generateBTCProjection(draft.btcAssumptions); + draft.macroOutputs = deriveMacroOutputs(draft.macroAssumptions, draft.btcOutputs); + (Object.keys(draft.modelOutputs) as ModelKind[]).forEach((model) => { + draft.modelOutputs[model] = deriveModelOutputs(model, draft.btcOutputs, draft.modelInputs[model]); + }); + draft.updatedAt = new Date().toISOString(); + }); + return { + scenarios: { ...state.scenarios, [id]: nextScenario } + }; + }), + updateMacroInputs: (id, updater) => + set((state) => { + const scenario = state.scenarios[id]; + if (!scenario) return state; + const nextScenario = produce(scenario, (draft) => { + updater(draft.macroAssumptions); + draft.macroOutputs = deriveMacroOutputs(draft.macroAssumptions, draft.btcOutputs); + draft.updatedAt = new Date().toISOString(); + }); + return { + scenarios: { ...state.scenarios, [id]: nextScenario } + }; + }), + updateModelInputs: (id, model, updater) => + set((state) => { + const scenario = state.scenarios[id]; + if (!scenario) return state; + const nextScenario = produce(scenario, (draft) => { + updater(draft.modelInputs[model]); + draft.modelOutputs[model] = deriveModelOutputs(model, draft.btcOutputs, draft.modelInputs[model]); + draft.updatedAt = new Date().toISOString(); + }); + return { + scenarios: { ...state.scenarios, [id]: nextScenario } + }; + }), + markStatus: (id, status) => + set((state) => { + const scenario = state.scenarios[id]; + if (!scenario) return state; + return { + scenarios: { + ...state.scenarios, + [id]: { + ...scenario, + status, + updatedAt: new Date().toISOString() + } + } + }; + }), + duplicateScenario: (id) => { + const scenario = get().scenarios[id]; + if (!scenario) return undefined; + const copyId = nanoid(); + const duplicated: ScenarioDetail = { + ...scenario, + id: copyId, + name: `${scenario.name} Copy`, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }; + set((state) => ({ + scenarios: { ...state.scenarios, [copyId]: duplicated }, + activeScenarioId: copyId + })); + return duplicated; + }, + deleteScenario: (id) => + set((state) => { + const next = { ...state.scenarios }; + delete next[id]; + const activeScenarioId = state.activeScenarioId === id ? null : state.activeScenarioId; + return { scenarios: next, activeScenarioId }; + }) + }), + { + name: 'scenario-store', + storage: createJSONStorage(() => localStorage) + } + ) +); diff --git a/src/types/scenario.ts b/src/types/scenario.ts new file mode 100644 index 0000000..61acd9e --- /dev/null +++ b/src/types/scenario.ts @@ -0,0 +1,63 @@ +export type ScenarioStatus = 'draft' | 'in-progress' | 'complete'; + +export type ModelKind = 'individual' | 'corporate' | 'institution' | 'nation'; + +export interface ScenarioSummary { + id: string; + name: string; + createdAt: string; + updatedAt: string; + status: ScenarioStatus; + model: ModelKind; + btcAssumptions: BTCInputs; + macroAssumptions: MacroInputs; +} + +export interface BTCInputs { + currentPrice: number; + arrStart: number; + arrDecay: number; + steadyStateArr: number; + steadyStateYear: number; +} + +export interface MacroInputs { + globalAssetBase: number; + adoptionStart: number; + adoptionEnd: number; + inflationDrift: number; + productivityBoost: number; +} + +export interface ModelInputs { + allocation: number; + treasuryShare: number; + cashflows: number; + leverage: number; +} + +export interface ScenarioDetail extends ScenarioSummary { + btcOutputs: Array; + macroOutputs: MacroOutputs; + modelOutputs: Record; + modelInputs: Record; +} + +export interface YearlyBTCRow { + year: number; + arr: number; + price: number; + marketCap: number; +} + +export interface MacroOutputs { + gdp: number[]; + adoptionShare: number[]; + liquidity: number[]; +} + +export interface ModelOutputs { + terminalValue: number; + btcHoldings: number[]; + cashflowCoverage: number[]; +} diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..71f38d7 --- /dev/null +++ b/tailwind.config.ts @@ -0,0 +1,43 @@ +import type { Config } from 'tailwindcss'; +import { fontFamily } from 'tailwindcss/defaultTheme'; + +const config: Config = { + darkMode: ['class'], + content: ['./app/**/*.{ts,tsx}', './src/**/*.{ts,tsx}'], + theme: { + extend: { + colors: { + bg: { + base: '#0B0E11', + surface: 'rgba(25, 32, 40, 0.85)' + }, + accent: { + primary: '#F7931A', + secondary: '#2DD4BF', + warning: '#F59E0B', + error: '#F87171' + }, + text: { + primary: '#F8FAFC', + secondary: '#94A3B8' + }, + border: { + subtle: 'rgba(148, 163, 184, 0.2)' + } + }, + fontFamily: { + sans: ['Inter', ...fontFamily.sans], + display: ['"Space Grotesk"', ...fontFamily.sans] + }, + boxShadow: { + glass: '0 20px 45px rgba(0,0,0,0.35)' + }, + backgroundImage: { + 'noise-gradient': 'linear-gradient(135deg, rgba(247,147,26,0.15), rgba(45,212,191,0.08))' + } + } + }, + plugins: [] +}; + +export default config; diff --git a/tests/e2e/smoke.spec.ts b/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000..3e21f7f --- /dev/null +++ b/tests/e2e/smoke.spec.ts @@ -0,0 +1,14 @@ +import { test, expect } from "@playwright/test"; + +test.describe("Bitcoin24 smoke page", () => { + test("renders the CI health check content", async ({ page }) => { + await page.goto("/"); + await expect(page.getByRole("heading", { name: "Bitcoin24 CI Health Check" })).toBeVisible(); + await expect(page.getByText("Score ≥ 95", { exact: false })).toBeVisible(); + }); +import { test, expect } from '@playwright/test'; + +test('placeholder smoke test', async ({ page }) => { + await page.goto('/'); + expect(true).toBeTruthy(); +}); diff --git a/tests/unit/metrics.test.ts b/tests/unit/metrics.test.ts new file mode 100644 index 0000000..2174e9a --- /dev/null +++ b/tests/unit/metrics.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; + +import { withinThreshold } from "../../src/metrics"; + +describe("withinThreshold", () => { + it("returns true when the metric value is within the SLO", () => { + expect( + withinThreshold( + { key: "lcp", threshold: 2000 }, + { key: "lcp", value: 1500 } + ) + ).toBe(true); + }); + + it("returns false when the metric value exceeds the SLO", () => { + expect( + withinThreshold( + { key: "apiLatency", threshold: 400 }, + { key: "apiLatency", value: 450 } + ) + ).toBe(false); + }); + + it("throws when comparing different metric keys", () => { + expect(() => + withinThreshold( + { key: "accessibility", threshold: 0.95 }, + { key: "lcp", value: 1800 } + ) + ).toThrowError(/Metric key mismatch/); + }); +}); diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..81eb634 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ES2021", + "lib": ["DOM", "DOM.Iterable", "ES2021"], + "module": "ESNext", + "moduleResolution": "Node", + "esModuleInterop": true, + "allowJs": false, + "strict": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "baseUrl": ".", + "paths": { + "@bitcoin24/ui": ["packages/ui/src"], + "@bitcoin24/ui/*": ["packages/ui/src/*"], + "@bitcoin24/config": ["packages/config/src"], + "@bitcoin24/config/*": ["packages/config/src/*"] + } + } + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "types": ["node"] + }, + "exclude": ["node_modules", "dist", "build", "coverage"] +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..f13420c --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "exclude": ["node_modules", "test", "dist", "**/*.spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d6bbe48 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,55 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "ESNext", + "moduleResolution": "Bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true, + "types": ["vitest/globals"] + }, + "include": ["src", "tests", "scripts"], + "target": "es2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": false, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "types": ["node"], + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@app/*": ["app/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "es2019", + "sourceMap": true, + "outDir": "dist", + "baseUrl": "./", + "incremental": true, + "strict": true, + "skipLibCheck": true, + "moduleResolution": "node", + "esModuleInterop": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..da1b664 --- /dev/null +++ b/turbo.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://turbo.build/schema.json", + "pipeline": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**", "build/**", "out/**"] + }, + "dev": { + "cache": false + }, + "lint": { + "outputs": [] + }, + "test": { + "dependsOn": ["^build"], + "outputs": ["coverage/**", "test-results/**"] + }, + "format": { + "outputs": [] + } + } +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..6d51419 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "jsdom", + coverage: { + reporter: ["text", "json"], + reportsDirectory: "coverage" + } + } +});