Skip to main content

Directory Overview

The project follows a standard Vite + React structure with clear separation of concerns:
raads-r-self-host/
├── src/
│   ├── components/      # React UI components
│   ├── hooks/           # Custom React hooks
│   ├── engine/          # Scoring logic
│   ├── data/            # Questionnaire data and schemas
│   ├── utils/           # Utility functions
│   ├── styles/          # Global styles
│   ├── assets/          # Static assets
│   ├── __tests__/       # Test files
│   ├── App.tsx          # Root component
│   ├── main.tsx         # Application entry point
│   └── index.css        # Root styles
├── public/              # Static files served as-is
├── dist/                # Production build output
├── package.json         # Dependencies and scripts
├── vite.config.ts       # Vite configuration
├── tsconfig.json        # TypeScript configuration
└── eslint.config.js     # ESLint configuration

Source Directory Structure

Components (src/components/)

React components organized by feature and responsibility:
components/
├── Landing.tsx          # Landing page with start button
├── Quiz.tsx             # Main quiz container
├── QuestionCard.tsx     # Individual question display
├── ProgressBar.tsx      # Quiz progress indicator
├── Results.tsx          # Results screen with scores
├── PrintView.tsx        # Print-friendly results view
├── DomainBar.tsx        # Domain score visualization
├── ExportButtons.tsx    # JSON/CSV/PDF export controls
├── HowScoringWorks.tsx  # Scoring methodology explanation
├── DarkModeToggle.tsx   # Dark/light mode switcher
├── PrivacyBanner.tsx    # localStorage consent banner
└── DeleteData.tsx       # Data deletion controls
Each component is self-contained with its own props interface defined inline. No separate types files are needed.

Hooks (src/hooks/)

Custom React hooks for state management and side effects:
hooks/
├── useQuizState.ts      # Main quiz state machine
├── useLocalStorage.ts   # localStorage persistence
└── useDarkMode.ts       # Dark mode state and system preference
Key Hook: useQuizState.ts This is the central state management hook that handles:
  • Quiz navigation (landing → quiz → results → print)
  • Response tracking
  • Score calculation
  • LocalStorage integration
  • Consent management

Engine (src/engine/)

Scoring algorithms and calculation logic:
engine/
├── scoring.ts           # Primary scoring algorithm
└── scoring-alt.ts       # Alternative scoring (lookup table)
Both scoring engines are cross-validated in tests to ensure accuracy. They use different approaches but must produce identical results.
Scoring Logic:
  • Normal items: score = response value (0-3)
  • Normative items: score = 3 - response value (reverse scoring)
  • Domain scores: sum of relevant item scores
  • Total score: sum of all domain scores (max 240)

Data (src/data/)

Questionnaire data and TypeScript schemas:
data/
├── dataset.json         # All 80 RAADS-R questions
└── dataset.schema.ts    # TypeScript types for dataset
Dataset Structure:
interface Dataset {
  meta: {
    totalItems: number;
    responseOptions: string[];
    domains: Domain[];
    totalCutoff: number;
  };
  items: Item[];
}

interface Item {
  id: number;
  text: string;
  isNormative: boolean;  // true = reverse scoring
  domain: string;        // "social" | "circumscribed" | "language" | "sensory"
}

Utils (src/utils/)

Utility functions for data manipulation:
utils/
└── export.ts            # JSON/CSV export helpers

Tests (src/__tests__/)

Test suites using Vitest:
__tests__/
├── scoring.test.ts      # Scoring algorithm validation
└── dataset.test.ts      # Dataset integrity checks
Test Coverage:
  • 62+ tests total
  • Golden test vectors for uniform responses
  • Cross-validation between scoring engines
  • Dataset structural integrity (no duplicates, all 80 items accounted for)

Configuration Files

vite.config.ts

Vite configuration with React and Tailwind plugins:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [react(), tailwindcss()],
  test: {
    globals: true,
  },
})

package.json

Key dependencies:
{
  "dependencies": {
    "react": "^19.2.0",
    "react-dom": "^19.2.0",
    "playwright": "^1.58.2"
  },
  "devDependencies": {
    "@tailwindcss/vite": "^4.2.1",
    "tailwindcss": "^4.2.1",
    "typescript": "~5.9.3",
    "vite": "^7.3.1",
    "vitest": "^4.0.18"
  }
}

TypeScript Configuration

  • tsconfig.json - Root TypeScript config
  • tsconfig.app.json - App-specific settings
  • tsconfig.node.json - Node environment settings (for Vite config)

Application Flow

1

Entry Point

src/main.tsx renders <App /> into the DOM
2

Root Component

src/App.tsx initializes state with useQuizState and renders the appropriate screen
3

Screen Routing

State machine handles transitions:
  • landingquizresultsprint
  • User can navigate back or retake the quiz
4

Data Persistence

useLocalStorage syncs state with localStorage (opt-in only)
5

Score Calculation

On quiz completion, scoring.ts calculates domain and total scores

Build Output

Running npm run build produces:
dist/
├── index.html           # Entry HTML file
├── assets/
│   ├── index-[hash].js  # Bundled JavaScript
│   └── index-[hash].css # Bundled CSS
└── vite.svg             # Favicon
The dist/ directory is git-ignored and should never be committed to version control.

Key Architectural Decisions

No Backend

All logic runs client-side. No API calls, no server, no database.

TypeScript Strict Mode

Full type safety with strict TypeScript configuration.

Tailwind v4

Utility-first CSS with CSS variables for theming (dark mode).

LocalStorage Only

Optional persistence with clear consent UI. No cookies, no tracking.

Dual Scoring Engines

Two independent implementations cross-validated in tests for accuracy.

Next Steps

Build docs developers (and LLMs) love