# Project export: txt2crs

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: Take any sort of input and turn that into a well researched, well-developed course. It will also create the study material and it will create a test on all the material along with answer key.
- Devpost: https://devpost.com/software/text-and-image-to-course-text2course
- GitHub: https://github.com/moshehbenavraham/txt2crs
- Video: https://www.youtube.com/embed/6ZO3wziZx40?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — moshehbenavraham (95 commits), dependabot[bot] (1 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

<p align="center">
  <img
    src="docs/txt2crs-readme-banner.webp"
    alt="One source passing through a research atelier and becoming four polished learning publications"
    width="100%"
  />
</p>

<h1 align="center">txt2crs</h1>

<p align="center">
  <strong>One bounded source in. A complete learning package out.</strong>
</p>

<p align="center">
  <a href="https://github.com/moshehbenavraham/txt2crs/actions/workflows/test-backend.yml"><img alt="Backend tests" src="https://img.shields.io/github/actions/workflow/status/moshehbenavraham/txt2crs/test-backend.yml?branch=main&style=for-the-badge&label=backend"></a>
  <a href="https://github.com/moshehbenavraham/txt2crs/actions/workflows/test-docker-compose.yml"><img alt="Docker Compose tests" src="https://img.shields.io/github/actions/workflow/status/moshehbenavraham/txt2crs/test-docker-compose.yml?branch=main&style=for-the-badge&label=compose"></a>
  <a href="https://github.com/moshehbenavraham/txt2crs/actions/workflows/security.yml"><img alt="Security checks" src="https://img.shields.io/github/actions/workflow/status/moshehbenavraham/txt2crs/security.yml?branch=main&style=for-the-badge&label=security"></a>
  <a href="https://github.com/moshehbenavraham/txt2crs/releases/tag/v1.3.1"><img alt="Release 1.3.1" src="https://img.shields.io/badge/release-v1.3.1-235a46?style=for-the-badge"></a>
</p>

<p align="center">
  <img alt="Python 3.14" src="https://img.shields.io/badge/Python-3.14-3776AB?style=flat-square&logo=python&logoColor=white">
  <img alt="FastAPI" src="https://img.shields.io/badge/FastAPI-0.141%2B-009688?style=flat-square&logo=fastapi&logoColor=white">
  <img alt="React 19" src="https://img.shields.io/badge/React-19-20232A?style=flat-square&logo=react&logoColor=61DAFB">
  <img alt="TypeScript 7" src="https://img.shields.io/badge/TypeScript-7-3178C6?style=flat-square&logo=typescript&logoColor=white">
  <img alt="PostgreSQL 18" src="https://img.shields.io/badge/PostgreSQL-18-4169E1?style=flat-square&logo=postgresql&logoColor=white">
  <img alt="OpenAI Codex" src="https://img.shields.io/badge/OpenAI-Codex-412991?style=flat-square&logo=openai&logoColor=white">
  <img alt="Docker Compose" src="https://img.shields.io/badge/Docker-Compose-2496ED?style=flat-square&logo=docker&logoColor=white">
  <img alt="MIT-0 and MIT licensed" src="https://img.shields.io/badge/license-MIT--0_%2B_MIT-c7922c?style=flat-square">
</p>

<p align="center">
  <a href="#quick-start">Quick start</a> ·
  <a href="#architecture">Architecture</a> ·
  <a href="#try-the-deterministic-sample">Deterministic sample</a> ·
  <a href="#testing">Testing</a> ·
  <a href="#privacy-and-current-limits">Privacy &amp; limits</a>
</p>

Give txt2crs a topic, pasted text, public URL, YouTube URL, PDF, DOCX, or
PPTX. It turns that single bounded source into a source-grounded course, review
pack, student assessment, and separate instructor answer key.

txt2crs combines a reusable Python generation engine, a durable FastAPI
application, and a warm, focused React learner experience. Docker Compose is
the complete reference deployment, and the same images can run on a hosted
container platform that preserves the documented security and state contract.

## What It Does

An authenticated learner can submit:

- a topic prompt or pasted text;
- a public URL or YouTube URL; or
- one bounded PDF, DOCX, or PPTX upload.

The application then:

1. validates and durably accepts the exact request before returning `202`;
2. ingests and policy-checks the source before provider work;
3. performs bounded Tavily research through a package-owned loopback MCP
   boundary;
4. runs the operator-selected exact Codex model with no silent fallback;
5. validates and checkpoints the generated learning structure;
6. renders four publications in HTML, Markdown, PDF, and DOCX; and
7. exposes exactly sixteen owner-private, integrity-checked artifacts.

| Publication | What the learner gets |
|---|---|
| **Course** | A structured curriculum with objectives, modules, lessons, and cited sources |
| **Review pack** | Key ideas, examples, study guidance, and focused practice |
| **Student assessment** | A complete test aligned to what the course actually teaches |
| **Instructor answer key** | Correct responses, explanations, and grading guidance kept separate from the student copy |

Every publication ships in **HTML, Markdown, PDF, and DOCX**: four useful
documents in four portable formats, for sixteen private artifacts in total.

Progress survives refreshes and process replacement. Results include bounded
source summaries and conflict disclosures, while the learner-facing API never
returns prompts, evidence excerpts, provider payloads, token data, checkpoint
JSON, artifact paths, or unrestricted file links.

The persistent owner-scoped course library lists retained work newest first,
uses opaque pagination, and reopens every active or completed request on its
existing durable job URL. The intake page also reads authoritative rolling
admission capacity before enabling another paid generation.

## Origin Story

Two days after I joined OpenAI Build Week, my Zimbabwean wife asked me:
"How can we bring AI to Africa?"

That question brought me back to an IBM AI Developer certification project: a
Make.com workflow that expanded learning material, created study guides, and
generated review questions. txt2crs turns that early experiment into a
durable product boundary for learners and instructors who need structured,
reviewable education material from almost any bounded source.

## Try The Deterministic Sample

The stable deterministic sample uses this synthetic request:

| Field | Value |
|-------|-------|
| Topic | Teach Python variables. |
| Learning goal | Explain and use Python variables. |
| Audience | Adult learner |
| Depth | Introductory |
| Duration | 60 minutes |
| Assessment items | 1 |

This credential-free fast gate runs without network access or provider
credentials through the public deterministic application factory. The
scenario exercises real durable request, checkpoint, validation, rendering,
and artifact stores, then proves four publications and sixteen private
artifacts.

Run the engine lifecycle:

```bash
cd backend/packages/txt2crs
uv run --package txt2crs pytest \
  tests/integration/test_application_lifecycle.py -q
```

Run the same journey through the real FastAPI and React boundary:

```bash
cd frontend
TXT2CRS_BROWSER_SCENARIO=complete npx playwright test \
  --config=playwright.jobs.config.ts \
  --project=chromium
```

See the complete
[deterministic sample contract](docs/release/DETERMINISTIC_SAMPLE_1_0_0.md)
and the separately identified
[live artifact inspection](docs/release/ARTIFACT_INSPECTION_1_0_0.md).

## Quick Start

### Prerequisites

- Docker Engine with Compose
- Git and Bash (Git Bash is sufficient on Windows)
- A ChatGPT subscription identity or OpenAI Platform API key for live Codex
  generation
- A Tavily API key for live research

Python, Node.js, uv, and npm are needed only for host-side development or
focused validation.

### 1. Configure local secrets

```bash
cp .env.example .env
```

Replace `SECRET_KEY`, `POSTGRES_PASSWORD`, and
`FIRST_SUPERUSER_PASSWORD` with independent values. Add the Tavily secret to
`TAVILY_API_KEY`. The established model remains the default; you may replace
it with any safe exact identifier reported by your Codex account:

```dotenv
TXT2CRS_MODEL_ID=gpt-5.6-sol
```

Do not commit `.env`. Public signup is enabled by default; set
`ENABLE_PUBLIC_SIGNUP=false` for an invite-only installation.

### 2. Start the complete application

```bash
./scripts/start-local.sh
```

The startup assistant validates `.env`, Docker, Compose, and local port
availability, then starts PostgreSQL first and verifies the configured password
over the same authenticated network path used by the backend. If a preserved
local volume still has an older password, the assistant updates that role in
place without deleting records or printing the

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 763 recognized source files, 5733 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- FastAPI (technology) — claimed on Devpost, not found in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (120 of 1175)

```
_typos.toml
.agents/skills/orchestrate-react-motion/agents/openai.yaml
.agents/skills/orchestrate-react-motion/references/motion-decision-cheatsheet.md
.agents/skills/orchestrate-react-motion/references/project-motion-baseline.md
.agents/skills/orchestrate-react-motion/references/react-motion-recipes.md
.agents/skills/orchestrate-react-motion/SKILL.md
.agents/skills/upgrade-react-visual-experience/agents/openai.yaml
.agents/skills/upgrade-react-visual-experience/references/project-frontend-map.md
.agents/skills/upgrade-react-visual-experience/references/visual-upgrade-playbook.md
.agents/skills/upgrade-react-visual-experience/SKILL.md
.context-profiles.yaml
.env.example
.gitattributes
.github/actionlint.yaml
.github/dependabot.yml
.github/DISCUSSION_TEMPLATE/questions.yml
.github/ISSUE_TEMPLATE/config.yml
.github/ISSUE_TEMPLATE/privileged.yml
.github/labeler.yml
.github/workflows/detect-conflicts.yml
.github/workflows/generate-client.yml
.github/workflows/guard-dependencies.yml
.github/workflows/playwright.yml
.github/workflows/quality.yml
.github/workflows/release.yml
.github/workflows/security.yml
.github/workflows/test-backend.yml
.github/workflows/test-docker-compose.yml
.github/workflows/zizmor.yml
.gitignore
.gitleaksignore
.pre-commit-config.yaml
.python-version
.spec_system/archive/phases/phase_00/PRD_phase_00.md
.spec_system/archive/phases/phase_00/session_01_baseline_container_and_state.md
.spec_system/archive/phases/phase_01/PRD_phase_01.md
.spec_system/archive/phases/phase_01/session_01_durable_requests_and_recovery.md
.spec_system/archive/phases/phase_01/session_02_safe_queries_and_artifact_access.md
.spec_system/archive/phases/phase_01/session_03_input_preferences_and_policy_gate.md
.spec_system/archive/phases/phase_01/session_04_managed_runtime_and_model_policy.md
.spec_system/archive/phases/phase_01/session_05_public_facade_and_owner_lifecycle.md
.spec_system/archive/phases/phase_03/PRD_phase_03.md
.spec_system/archive/phases/phase_03/session_01_durable_job_submission_and_admission.md
.spec_system/archive/phases/phase_03/session_02_owner_scoped_job_results_and_recovery.md
.spec_system/archive/phases/phase_03/session_03_account_purge_and_donor_retirement.md
.spec_system/archive/phases/phase_04/PRD_phase_04.md
.spec_system/archive/phases/phase_04/session_01_public_landing_intake_and_progress.md
.spec_system/archive/phases/phase_04/session_02_results_preview_and_experience_validation.md
.spec_system/archive/phases/phase_05/PRD_phase_05.md
.spec_system/archive/phases/phase_05/session_01_release_hardening_and_live_proof.md
.spec_system/archive/phases/phase_05/session_02_submission_assets_and_devpost.md
.spec_system/archive/PRD/PRD-backup-20260719-083759.md
.spec_system/archive/sessions/phase01-session01-durable-requests-and-recovery/code-review.md
.spec_system/archive/sessions/phase01-session01-durable-requests-and-recovery/IMPLEMENTATION_SUMMARY.md
.spec_system/archive/sessions/phase01-session01-durable-requests-and-recovery/implementation-notes.md
.spec_system/archive/sessions/phase01-session01-durable-requests-and-recovery/security-compliance.md
.spec_system/archive/sessions/phase01-session01-durable-requests-and-recovery/spec.md
.spec_system/archive/sessions/phase01-session01-durable-requests-and-recovery/tasks.md
.spec_system/archive/sessions/phase01-session01-durable-requests-and-recovery/validation.md
.spec_system/archive/sessions/phase01-session02-safe-queries-and-artifact-access/code-review.md
.spec_system/archive/sessions/phase01-session02-safe-queries-and-artifact-access/IMPLEMENTATION_SUMMARY.md
.spec_system/archive/sessions/phase01-session02-safe-queries-and-artifact-access/implementation-notes.md
.spec_system/archive/sessions/phase01-session02-safe-queries-and-artifact-access/security-compliance.md
.spec_system/archive/sessions/phase01-session02-safe-queries-and-artifact-access/spec.md
.spec_system/archive/sessions/phase01-session02-safe-queries-and-artifact-access/tasks.md
.spec_system/archive/sessions/phase01-session02-safe-queries-and-artifact-access/validation.md
.spec_system/archive/sessions/phase01-session03-input-preferences-and-policy-gate/code-review.md
.spec_system/archive/sessions/phase01-session03-input-preferences-and-policy-gate/IMPLEMENTATION_SUMMARY.md
.spec_system/archive/sessions/phase01-session03-input-preferences-and-policy-gate/implementation-notes.md
.spec_system/archive/sessions/phase01-session03-input-preferences-and-policy-gate/security-compliance.md
.spec_system/archive/sessions/phase01-session03-input-preferences-and-policy-gate/spec.md
.spec_system/archive/sessions/phase01-session03-input-preferences-and-policy-gate/tasks.md
.spec_system/archive/sessions/phase01-session03-input-preferences-and-policy-gate/validation.md
.spec_system/archive/sessions/phase01-session04-managed-runtime-and-model-policy/code-review.md
.spec_system/archive/sessions/phase01-session04-managed-runtime-and-model-policy/IMPLEMENTATION_SUMMARY.md
.spec_system/archive/sessions/phase01-session04-managed-runtime-and-model-policy/implementation-notes.md
.spec_system/archive/sessions/phase01-session04-managed-runtime-and-model-policy/security-compliance.md
.spec_system/archive/sessions/phase01-session04-managed-runtime-and-model-policy/spec.md
.spec_system/archive/sessions/phase01-session04-managed-runtime-and-model-policy/tasks.md
.spec_system/archive/sessions/phase01-session04-managed-runtime-and-model-policy/validation.md
.spec_system/archive/sessions/phase01-session05-public-facade-and-owner-lifecycle/code-review.md
.spec_system/archive/sessions/phase01-session05-public-facade-and-owner-lifecycle/IMPLEMENTATION_SUMMARY.md
.spec_system/archive/sessions/phase01-session05-public-facade-and-owner-lifecycle/implementation-notes.md
.spec_system/archive/sessions/phase01-session05-public-facade-and-owner-lifecycle/security-compliance.md
.spec_system/archive/sessions/phase01-session05-public-facade-and-owner-lifecycle/spec.md
.spec_system/archive/sessions/phase01-session05-public-facade-and-owner-lifecycle/tasks.md
.spec_system/archive/sessions/phase01-session05-public-facade-and-owner-lifecycle/validation.md
.spec_system/archive/sessions/phase02-session01-engine-composition-lifecycle/code-review.md
.spec_system/archive/sessions/phase02-session01-engine-composition-lifecycle/IMPLEMENTATION_SUMMARY.md
.spec_system/archive/sessions/phase02-session01-engine-composition-lifecycle/implementation-notes.md
.spec_system/archive/sessions/phase02-session01-engine-composition-lifecycle/security-compliance.md
.spec_system/archive/sessions/phase02-session01-engine-composition-lifecycle/spec.md
.spec_system/archive/sessions/phase02-session01-engine-composition-lifecycle/tasks.md
.spec_system/archive/sessions/phase02-session01-engine-composition-lifecycle/validation.md
.spec_system/archive/sessions/phase02-session02-serial-worker-supervisor/code-review.md
.spec_system/archive/sessions/phase02-session02-serial-worker-supervisor/IMPLEMENTATION_SUMMARY.md
.spec_system/archive/sessions/phase02-session02-serial-worker-supervisor/implementation-notes.md
.spec_system/archive/sessions/phase02-session02-serial-worker-supervisor/security-compliance.md
.spec_system/archive/sessions/phase02-session02-serial-worker-supervisor/spec.md
.spec_system/archive/sessions/phase02-session02-serial-worker-supervisor/tasks.md
.spec_system/archive/sessions/phase02-session02-serial-worker-supervisor/validation.md
.spec_system/archive/sessions/phase02-session03-cached-readiness-and-observability/code-review.md
.spec_system/archive/sessions/phase02-session03-cached-readiness-and-observability/IMPLEMENTATION_SUMMARY.md
.spec_system/archive/sessions/phase02-session03-cached-readiness-and-observability/implementation-notes.md
.spec_system/archive/sessions/phase02-session03-cached-readiness-and-observability/security-compliance.md
.spec_system/archive/sessions/phase02-session03-cached-readiness-and-observability/spec.md
.spec_system/archive/sessions/phase02-session03-cached-readiness-and-observability/tasks.md
.spec_system/archive/sessions/phase02-session03-cached-readiness-and-observability/validation.md
.spec_system/archive/sessions/phase02-session04-system-readiness-and-auth-api/code-review.md
.spec_system/archive/sessions/phase02-session04-system-readiness-and-auth-api/IMPLEMENTATION_SUMMARY.md
.spec_system/archive/sessions/phase02-session04-system-readiness-and-auth-api/implementation-notes.md
.spec_system/archive/sessions/phase02-session04-system-readiness-and-auth-api/security-compliance.md
.spec_system/archive/sessions/phase02-session04-system-readiness-and-auth-api/spec.md
.spec_system/archive/sessions/phase02-session04-system-readiness-and-auth-api/tasks.md
.spec_system/archive/sessions/phase02-session04-system-readiness-and-auth-api/validation.md
.spec_system/archive/sessions/phase02-session05-operator-setup-experience/code-review.md
.spec_system/archive/sessions/phase02-session05-operator-setup-experience/IMPLEMENTATION_SUMMARY.md
.spec_system/archive/sessions/phase02-session05-operator-setup-experience/implementation-notes.md
.spec_system/archive/sessions/phase02-session05-operator-setup-experience/security-compliance.md
.spec_system/archive/sessions/phase02-session05-operator-setup-experience/spec.md
[1055 more files omitted for size]
```

### Dependencies

- backend/packages/txt2crs/pyproject.toml: faster-whisper@>=1.2,<2, httpx@>=0.28,<1, mcp@>=1.28,<2, openai-codex@==0.144.4, openai-codex-cli-bin@==0.144.4, pillow@>=11,<13, pydantic@>=2.12,<3, pymupdf@>=1.26,<2, pytesseract@>=0.3.13,<1, python-docx@>=1.2,<2, python-pptx@>=1.0,<2, uvicorn@>=0.41,<1, youtube-transcript-api@>=1.2,<2
- backend/pyproject.toml: alembic@>=1.18.5,<2.0.0, email-validator@>=2.3.0,<3.0.0, emails@>=1.1.2,<2.0.0, fastapi[standard]@>=0.139.2,<1.0.0, httpx@>=0.28.1,<1.0.0, jinja2@>=3.1.6,<4.0.0, mcp@>=1.28.1,<2.0.0, opentelemetry-api@>=1.44.0,<2.0.0, opentelemetry-exporter-otlp@>=1.44.0,<2.0.0, opentelemetry-instrumentation-fastapi@>=0.65b0,<1.0.0, opentelemetry-instrumentation-httpx@>=0.65b0,<1.0.0, opentelemetry-instrumentation-sqlalchemy@>=0.65b0,<1.0.0, opentelemetry-sdk@>=1.44.0,<2.0.0, psycopg[binary]@>=3.3.4,<4.0.0, pwdlib[argon2,bcrypt]@>=0.3.0,<1.0.0, pydantic@>=2.13.4,<3.0.0, pydantic-settings@>=2.14.2,<3.0.0, pyjwt@>=2.13.0,<3.0.0, python-multipart@>=0.0.32,<1.0.0, sentry-sdk[fastapi]@>=2.66.0,<3.0.0, slowapi@>=0.1.10,<1.0.0, sqlmodel@>=0.0.39,<1.0.0, starlette@>=1.3.1,<2.0.0, tenacity@>=9.1.4,<10.0.0, txt2crs
- frontend/package.json: @biomejs/biome@^2.5.4, @hey-api/openapi-ts@0.99.0, @hookform/resolvers@^5.4.0, @playwright/test@1.61.1, @radix-ui/react-avatar@^1.2.2, @radix-ui/react-checkbox@^1.3.7, @radix-ui/react-collapsible@^1.1.16, @radix-ui/react-dialog@^1.1.19, @radix-ui/react-dropdown-menu@^2.1.20, @radix-ui/react-label@^2.1.11, @radix-ui/react-radio-group@^1.4.3, @radix-ui/react-scroll-area@^1.2.14, @radix-ui/react-select@^2.3.3, @radix-ui/react-separator@^1.1.11, @radix-ui/react-slot@^1.3.0, @radix-ui/react-tabs@^1.1.17, @radix-ui/react-tooltip@^1.2.12, @tailwindcss/vite@^4.3.3, @tanstack/react-query@^5.101.2, @tanstack/react-router@^1.170.18, @tanstack/react-table@^8.21.3, @tanstack/router-plugin@^1.168.20, @types/node@^26.1.1, @types/react@^19.2.17, @types/react-dom@^19.2.3, @vitejs/plugin-react@^6.0.3, class-variance-authority@^0.7.1, clsx@^2.1.1, dotenv@^17.4.2, lucide-react@^1.25.0, next-themes@^0.4.6, react@^19.2.7, react-dom@^19.2.7, react-error-boundary@^6.1.2, react-hook-form@^7.81.0, react-icons@^5.7.0, react-markdown@^10.1.0, sonner@^2.0.7, tailwind-merge@^3.6.0, tailwindcss@^4.3.3, tw-animate-css@^1.4.0, typescript@^7.0.2, vite@^8.1.5, vitest@^4.1.10, zod@^4.4.3

### Recent commits (newest first)

- Patch development YAML parser advisory
- Prepare public Build Week submission release
- Improve research evidence coverage and planning
- feat: ship publication design system v1.2.1
- Fix platform checks and consolidate documentation
- feat: expose owner admission capacity
- fix: harden course monitoring and release 1.1.4
- fix: report idle runtime ownership as ready
- chore: release version 1.1.2
- fix: harden course generation reliability
- feat: add owner course library and release 1.1.0
- feat: improve setup and course progress feedback
- fix: synchronize readiness input modes
- fix: harden local startup recovery
- fix: assign collision-free local ports
- feat: add judge-ready local startup
- Reconcile Phase 05 documentation
- Carry Phase 05 lessons forward
- Validate Phase 05 infrastructure
- Harden GitHub workflow safety

## Key source files (fetched from GitHub, selected and truncated for size)

### CLAUDE.md

```markdown
AGENTS.md
```

### GEMINI.md

```markdown
AGENTS.md
```

### docker-compose.yml

```yaml
services:

  db:
    image: postgres:18.4
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      retries: 5
      start_period: 30s
      timeout: 10s
    volumes:
      - app-db-data:/var/lib/postgresql/data/pgdata
    env_file:
      - .env
    environment:
      - PGDATA=/var/lib/postgresql/data/pgdata
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set}
      - POSTGRES_USER=${POSTGRES_USER?Variable not set}
      - POSTGRES_DB=${POSTGRES_DB?Variable not set}

  prestart:
    image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}'
    build:
      context: ./backend
    networks:
      - traefik-public
      - default
    depends_on:
      db:
        condition: service_healthy
        restart: true
    command: bash scripts/prestart.sh
    env_file:
      - .env
    environment:
      - DOMAIN=${DOMAIN}
      - FRONTEND_HOST=${FRONTEND_HOST?Variable not set}
      - ENVIRONMENT=${ENVIRONMENT}
      - BACKEND_CORS_ORIGINS=${BACKEND_CORS_ORIGINS}
      - SECRET_KEY=${SECRET_KEY?Variable not set}
      - FIRST_SUPERUSER=${FIRST_SUPERUSER?Variable not set}
      - FIRST_SUPERUSER_PASSWORD=${FIRST_SUPERUSER_PASSWORD?Variable not set}
      - SMTP_HOST=${SMTP_HOST}
      - SMTP_USER=${SMTP_USER}
      - SMTP_PASSWORD=${SMTP_PASSWORD}
      - EMAILS_FROM_EMAIL=${EMAILS_FROM_EMAIL}
      - POSTGRES_SERVER=db
      # Containers reach PostgreSQL on its service port. POSTGRES_PORT from
      # .env is only the host mapping used by local, non-container commands.
      - POSTGRES_PORT=5432
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER?Variable not set}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set}
      - SENTRY_DSN=${SENTRY_DSN}
      # Prestart imports the same validated shell settings as the API process.
      # Keep its view of private engine paths identical without mounting or
      # writing the runtime state volume during database migrations.
      - TXT2CRS_STATE_ROOT=/var/lib/txt2crs
      - TXT2CRS_JOB_DB_PATH=/var/lib/txt2crs/jobs.sqlite3
      - TXT2CRS_ARTIFACT_ROOT=/var/lib/txt2crs/artifacts
      - TXT2CRS_CODEX_HOME=/var/lib/txt2crs/codex-home
      - TXT2CRS_WORKER_ROOT=/tmp/txt2crs-worker

  backend:
    image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}'
    restart: always
    networks:
      - traefik-public
      - default
    depends_on:
      db:
        condition: service_healthy
        restart: true
      prestart:
        condition: service_completed_successfully
    env_file:
      - .env
    environment:
      - DOMAIN=${DOMAIN}
      - FRONTEND_HOST=${FRONTEND_HOST?Variable not set}
      - ENVIRONMENT=${ENVIRONMENT}
      - BACKEND_CORS_ORIGINS=${BACKEND_CORS_ORIGINS}
      - SECRET_KEY=${SECRET_KEY?Variable not set}
      - FIRST_SUPERUSER=${FIRST_SUPERUSER?Variable not set}
      - FIRST_SUPERUSER_PASSWORD=${FIRST_SUPERUSER_PASSWORD?Variable not set}
      - SMTP_HOST=${SMTP_HOST}
      - SMTP_USER=${SMTP_USER}
      - SMTP_PASSWORD=${SMTP_PASSWORD}
      - EMAILS_FROM_EMAIL=${EMAILS_FROM_EMAIL}
      - POSTGRES_SERVER=db
      # Service-to-service traffic uses PostgreSQL's container port even when
      # a different host port is published for local tools.
      - POSTGRES_PORT=5432
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER?Variable not set}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set}
      - SENTRY_DSN=${SENTRY_DSN}
      # These paths match the owner-only directories baked into the image.
      # Host-only runs may override Settings, but a container mount target must
      # not move without rebuilding the image with matching ownership.
      - TXT2CRS_STATE_ROOT=/var/lib/txt2crs
      - TXT2CRS_JOB_DB_PATH=/var/lib/txt2crs/jobs.sqlite3
      - TXT2CRS_ARTIFACT_ROOT=/var/lib/txt2crs/artifacts
      - TXT2CRS_CODEX_HOME=/var/lib/txt2crs/codex-home
      - TXT2CRS_WORKER_ROOT=/tmp/txt2crs-worker

    # One application-owned volume preserves SQLite/WAL files, artifacts, and
    # Codex-managed credentials together across container replacement.
    volumes:
      - txt2crs-state:/var/lib/txt2crs

    healthcheck:
      test:
        [
          "CMD",
          "python",
          "-c",
          "import json, urllib.request; response = urllib.request.urlopen('http://localhost:8000/api/v1/utils/health/', timeout=5); data = json.load(response); raise SystemExit(0 if data.get('status') == 'healthy' else 1)",
        ]
      interval: 10s
      timeout: 5s
      retries: 5

    build:
      context: ./backend
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik-public
      - traefik.constraint-label=traefik-public

      - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=8000

      - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`api.${DOMAIN?Variable not set}`)
      - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.entrypoints=http

      - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`api.${DOMAIN?Variable not set}`)
      - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.entrypoints=https
      - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls=true
      - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls.certresolver=le

      # Enable redirection for HTTP and HTTPS
      - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.middlewares=https-redirect

  frontend:
    image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}'
    restart: always
    networks:
      - traefik-public
      - default
    build:
      context: ./frontend
      args:
        - VITE_API_URL=https://api.${DOMAIN?Variable not set}
        - VITE_ENABLE_PUBLIC_SIGNUP=${ENABLE_PUBLIC_SIGNUP:-false}
        - VITE_HTML_PREVIEW_MAX_BYTES=${TXT2CRS_HTML_PREVIEW_MAX
[truncated — 1175 more characters]
```

### frontend/Dockerfile

```
# Stage 0, "build-stage", based on Node.js, to build and compile the frontend
FROM node:26.5.0 AS build-stage

WORKDIR /app

RUN npm install --global npm@12.0.1

COPY package*.json /app/

RUN npm ci

COPY ./ /app/

# Docker Compose or a direct local build supplies this argument. Unset is valid
# for relative/local API configurations and avoids an undefined self-default.
ARG VITE_API_URL
# Non-secret display configuration. Authorization remains backend-owned.
ARG VITE_ENABLE_PUBLIC_SIGNUP=false
# Non-secret preview presentation bound; private delivery remains backend-owned.
ARG VITE_HTML_PREVIEW_MAX_BYTES=5242880

RUN npm run build


# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx
FROM nginx:1.31.3

COPY --from=build-stage /app/dist/ /usr/share/nginx/html

COPY ./nginx.conf /etc/nginx/conf.d/default.conf
COPY ./nginx-backend-not-found.conf /etc/nginx/extra-conf.d/backend-not-found.conf

# Check Nginx from inside the container so Docker Compose can reject an image
# that started a process but cannot serve frontend requests.
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
    CMD curl --fail --silent --show-error http://127.0.0.1/health > /dev/null || exit 1

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.3.3",
  "packageManager": "npm@12.0.1",
  "engines": {
    "node": ">=26.5.0 <27",
    "npm": ">=12.0.1 <13"
  },
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -p tsconfig.build.json && vite build",
    "lint": "biome check --write --unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./",
    "typecheck": "tsc -p tsconfig.build.json --noEmit",
    "preview": "vite preview",
    "generate-client": "../scripts/generate-client.sh",
    "generate-client:codegen": "node scripts/generate-client.mjs && biome check --write --unsafe --no-errors-on-unmatched --files-ignore-unknown=true openapi.json src/client && node scripts/normalize-generated-client.mjs",
    "test:unit": "vitest run src",
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui"
  },
  "dependencies": {
    "@hookform/resolvers": "^5.4.0",
    "@radix-ui/react-avatar": "^1.2.2",
    "@radix-ui/react-checkbox": "^1.3.7",
    "@radix-ui/react-collapsible": "^1.1.16",
    "@radix-ui/react-dialog": "^1.1.19",
    "@radix-ui/react-dropdown-menu": "^2.1.20",
    "@radix-ui/react-label": "^2.1.11",
    "@radix-ui/react-radio-group": "^1.4.3",
    "@radix-ui/react-scroll-area": "^1.2.14",
    "@radix-ui/react-select": "^2.3.3",
    "@radix-ui/react-separator": "^1.1.11",
    "@radix-ui/react-slot": "^1.3.0",
    "@radix-ui/react-tabs": "^1.1.17",
    "@radix-ui/react-tooltip": "^1.2.12",
    "@tailwindcss/vite": "^4.3.3",
    "@tanstack/react-query": "^5.101.2",
    "@tanstack/react-router": "^1.170.18",
    "@tanstack/react-table": "^8.21.3",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^1.25.0",
    "next-themes": "^0.4.6",
    "react": "^19.2.7",
    "react-dom": "^19.2.7",
    "react-error-boundary": "^6.1.2",
    "react-hook-form": "^7.81.0",
    "react-icons": "^5.7.0",
    "react-markdown": "^10.1.0",
    "sonner": "^2.0.7",
    "tailwind-merge": "^3.6.0",
    "tailwindcss": "^4.3.3",
    "zod": "^4.4.3"
  },
  "devDependencies": {
    "@biomejs/biome": "^2.5.4",
    "@hey-api/openapi-ts": "0.99.0",
    "@playwright/test": "1.61.1",
    "@tanstack/router-plugin": "^1.168.20",
    "@types/node": "^26.1.1",
    "@types/react": "^19.2.17",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^6.0.3",
    "dotenv": "^17.4.2",
    "tw-animate-css": "^1.4.0",
    "typescript": "^7.0.2",
    "vite": "^8.1.5",
    "vitest": "^4.1.10"
  },
  "overrides": {
    "js-yaml": "4.3.0"
  }
}

```

### backend/pyproject.toml

```
# The backend is both the FastAPI application-shell project ("app") and the
# uv workspace root. The independently installable txt2crs education engine
# lives in packages/txt2crs and is consumed as a workspace dependency.

[project]
name = "app"
version = "0.3.6"
description = "txt2crs FastAPI application shell"
requires-python = ">=3.14,<3.15"
dependencies = [
    "fastapi[standard]>=0.139.2,<1.0.0",
    "starlette>=1.3.1,<2.0.0",
    "python-multipart>=0.0.32,<1.0.0",
    "email-validator>=2.3.0,<3.0.0",
    "pwdlib[argon2,bcrypt]>=0.3.0,<1.0.0",
    "tenacity>=9.1.4,<10.0.0",
    "pydantic>=2.13.4,<3.0.0",
    "emails>=1.1.2,<2.0.0",
    "jinja2>=3.1.6,<4.0.0",
    "alembic>=1.18.5,<2.0.0",
    "httpx>=0.28.1,<1.0.0",
    "psycopg[binary]>=3.3.4,<4.0.0",
    "sqlmodel>=0.0.39,<1.0.0",
    "pydantic-settings>=2.14.2,<3.0.0",
    "sentry-sdk[fastapi]>=2.66.0,<3.0.0",
    "pyjwt>=2.13.0,<3.0.0",
    "slowapi>=0.1.10,<1.0.0",
    # OpenTelemetry for distributed tracing (all packages must use same version)
    "opentelemetry-api>=1.44.0,<2.0.0",
    "opentelemetry-sdk>=1.44.0,<2.0.0",
    "opentelemetry-exporter-otlp>=1.44.0,<2.0.0",
    "opentelemetry-instrumentation-fastapi>=0.65b0,<1.0.0",
    "opentelemetry-instrumentation-sqlalchemy>=0.65b0,<1.0.0",
    "opentelemetry-instrumentation-httpx>=0.65b0,<1.0.0",
    # MCP (Model Context Protocol) for AI agent tool access
    "mcp>=1.28.1,<2.0.0",
    # The txt2crs education engine (workspace package)
    "txt2crs",
]

[dependency-groups]
dev = [
    "httpx2>=2.7.0,<3.0.0",
    "pytest>=9.1.1,<10.0.0",
    "pytest-cov>=7.1.0,<8.0.0",
    "mypy>=2.3.0,<3.0.0",
    "ty>=0.0.25,<1.0.0",
    "ruff>=0.15.22,<1.0.0",
    "pre-commit>=4.6.0,<5.0.0",
    "coverage>=7.15.2,<8.0.0",
    "hypothesis>=6.156.6,<7.0.0",
    "zizmor>=1.25.2,<2.0.0",
]

[tool.uv.workspace]
members = ["packages/txt2crs"]

[tool.uv.sources]
txt2crs = { workspace = true }

[build-system]
requires = ["hatchling>=1.31.0,<2.0.0"]
build-backend = "hatchling.build"

[tool.mypy]
strict = true
exclude = ["venv", ".venv", "alembic", "packages"]

[tool.ruff]
target-version = "py314"
exclude = ["alembic", "packages"]

[tool.ruff.lint]
select = [
    "E",  # pycodestyle errors
    "W",  # pycodestyle warnings
    "F",  # pyflakes
    "I",  # isort
    "B",  # flake8-bugbear
    "C4",  # flake8-comprehensions
    "UP",  # pyupgrade
    "ARG001", # unused arguments in functions
    "T201",   # print statements are not allowed
]
ignore = [
    "E501",  # line too long, handled by black
    "B008",  # do not perform function calls in argument defaults
    "W191",  # indentation contains tabs
    "B904",  # Allow raising exceptions without from e, for HTTPException
]

[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true

[tool.coverage.run]
source = ["app"]
dynamic_context = "test_function"

[tool.coverage.report]
show_missing = true
sort = "-Cover"

[tool.coverage.html]
show_contexts = true

[tool.pytest.ini_options]
markers = [
    "integration: marks tests as integration tests that require external API access (deselect with '-m \"not integration\"')",
    "hypothesis: marks tests as property-based tests using Hypothesis",
]
filterwarnings = [
    # Third-party library warnings that cannot be fixed in this project
    "ignore:Please use `import python_multipart`:PendingDeprecationWarning:starlette",
    "ignore:.*iscoroutinefunction.*deprecated.*:DeprecationWarning:slowapi.extension",
]

[tool.hypothesis]
# Property-based testing configuration
# See: https://hypothesis.readthedocs.io/en/latest/settings.html
deadline = 500  # ms - allow slower tests in CI
max_examples = 100  # Balance thoroughness with speed
suppress_health_check = ["too_slow", "filter_too_much"]

[tool.ty.terminal]
error-on-warning = true

[tool.fastapi]
entrypoint = "app.main:app"

```

### backend/Dockerfile

```
# =============================================================================
# Stage 1: base - shared dependency installation
# =============================================================================
FROM python:3.14.6 AS base

ENV PYTHONUNBUFFERED=1

WORKDIR /app/

# Codex can fall back to its bundled sandbox helper, but it logs the missing
# system binary at error severity first. Install the small reviewed Debian
# package so healthy startup is quiet and operators are not sent after a false
# incident signal.
RUN apt-get update && \
    apt-get install --no-install-recommends -y bubblewrap && \
    rm -rf /var/lib/apt/lists/*

# Install uv
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv
COPY --from=ghcr.io/astral-sh/uv:0.11.29 /uv /uvx /bin/

# Place executables in the environment at the front of the path
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#using-the-environment
ENV PATH="/app/.venv/bin:$PATH"

# Compile bytecode
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#compiling-bytecode
ENV UV_COMPILE_BYTECODE=1

# uv Cache
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#caching
ENV UV_LINK_MODE=copy

ENV PYTHONPATH=/app

COPY ./pyproject.toml ./uv.lock ./alembic.ini /app/

# The backend project declares txt2crs as a uv workspace dependency. uv must
# see the workspace member before the first sync; otherwise a host install can
# work while the production image fails because `packages/txt2crs` is absent.
COPY ./packages /app/packages

# =============================================================================
# Stage 2: production-like image used by local release validation
# Target with: docker build --target production
# =============================================================================
FROM base AS production

# Install production dependencies only (no dev/test packages)
RUN --mount=type=cache,target=/root/.cache/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv sync --frozen --no-install-project --no-dev

COPY ./scripts /app/scripts
COPY ./app /app/app

# Sync the project (production deps only)
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --no-dev

# Create the same fixed-UID runtime identity used by local Compose. The private
# mount points are seeded with owner-only permissions so a fresh named volume
# keeps the correct ownership when Docker initializes it from the image.
RUN groupadd --gid 1001 appuser && \
    useradd --uid 1001 --gid appuser --no-create-home --shell /usr/sbin/nologin appuser && \
    install -d -m 0700 -o appuser -g appuser \
        /var/lib/txt2crs \
        /var/lib/txt2crs/artifacts \
        /var/lib/txt2crs/codex-home \
        /tmp/txt2crs-worker && \
    chown -R appuser:appuser /app

USER appuser

EXPOSE 8000

# Health check using Python stdlib (no curl needed in production image)
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD python -c "import json, urllib.request; response = urllib.request.urlopen('http://localhost:8000/api/v1/utils/health/', timeout=5); data = json.load(response); raise SystemExit(0 if data.get('status') == 'healthy' else 1)" || exit 1

# P0 owns one serial engine worker in-process, so the ASGI server must also
# remain a single process. Horizontal scaling requires a future queue design.
CMD ["fastapi", "run", "app/main.py"]

# =============================================================================
# Stage 3: development - local dev with tests (default when no target specified)
# Used by: docker compose build
# =============================================================================
FROM base AS development

# Install ALL dependencies (including dev)
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
RUN --mount=type=cache,target=/root/.cache/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv sync --frozen --no-install-project

COPY ./scripts /app/scripts
COPY ./app /app/app
COPY ./tests /app/tests

# Sync the project
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync

# Development runs application code as the same non-root identity as
# production. Tests that truly need another identity must use a dedicated test
# target instead of weakening the application image.
RUN groupadd --gid 1001 appuser && \
    useradd --uid 1001 --gid appuser --no-create-home --shell /usr/sbin/nologin appuser && \
    install -d -m 0700 -o appuser -g appuser \
        /var/lib/txt2crs \
        /var/lib/txt2crs/artifacts \
        /var/lib/txt2crs/codex-home \
        /tmp/txt2crs-worker && \
    chown -R appuser:appuser /app

USER appuser

CMD ["fastapi", "run", "app/main.py"]

```

### backend/packages/txt2crs/pyproject.toml

```
[project]
name = "txt2crs"
version = "1.2.5"
description = "Turn source material into researched courses, review packs, and assessments."
readme = "README_txt2crs.md"
requires-python = ">=3.12,<3.15"
license = "MIT-0 AND MIT"
license-files = ["LICENSE", "THIRD_PARTY_NOTICES.md"]
keywords = [
    "assessment",
    "course-generation",
    "education",
    "openai",
    "study-materials",
]
classifiers = [
    "Development Status :: 5 - Production/Stable",
    "Intended Audience :: Education",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
]
dependencies = [
    "httpx>=0.28,<1",
    "mcp>=1.28,<2",
    "openai-codex==0.144.4",
    "openai-codex-cli-bin==0.144.4",
    "pillow>=11,<13",
    "pydantic>=2.12,<3",
    "pymupdf>=1.26,<2",
    "pytesseract>=0.3.13,<1",
    "python-docx>=1.2,<2",
    "python-pptx>=1.0,<2",
    # The engine owns a pre-bound loopback ASGI listener for research MCP.
    "uvicorn>=0.41,<1",
    "youtube-transcript-api>=1.2,<2",
]

[project.scripts]
# Browser and terminal recovery both use the same package-owned authentication
# service, so operators keep a supported fallback when the app is unavailable.
txt2crs-system-auth = "txt2crs.ai.system_authentication_cli:main"

[project.optional-dependencies]
# Local audio/video transcription is intentionally optional because the
# maintained runtime includes large native model dependencies.
transcription = [
    "faster-whisper>=1.2,<2",
]

[project.urls]
Documentation = "https://github.com/moshehbenavraham/txt2crs/tree/main/backend/packages/txt2crs/docs"
Issues = "https://github.com/moshehbenavraham/txt2crs/issues"
Repository = "https://github.com/moshehbenavraham/txt2crs"

[dependency-groups]
dev = [
    "build>=1.2,<2",
    "mypy>=1.14,<3",
    "pytest>=8.3,<10",
    "ruff>=0.9,<1",
]

[build-system]
requires = ["hatchling>=1.27,<2"]
build-backend = "hatchling.build"

[tool.hatch.build]
# Placeholder files keep the planned responsibility folders visible in Git,
# but they are development scaffolding and should not ship to library users.
exclude = ["/.gitignore", "**/.gitkeep"]

[tool.hatch.build.targets.sdist]
include = [
    "/docs",
    "/src",
    "/tests",
    "/LICENSE",
    "/README_txt2crs.md",
    "/THIRD_PARTY_NOTICES.md",
    "/pyproject.toml",
]

[tool.hatch.build.targets.wheel]
packages = ["src/txt2crs"]

[tool.pytest.ini_options]
addopts = ["--import-mode=importlib", "-ra"]
testpaths = ["tests"]
markers = [
    "live: explicitly enabled tests using an authenticated external provider",
]

[tool.mypy]
strict = true
files = ["src", "tests"]

[tool.ruff]
target-version = "py312"
extend-exclude = ["dist"]

[tool.ruff.lint]
select = [
    "B",   # flake8-bugbear catches likely logic errors.
    "C4",  # flake8-comprehensions encourages readable comprehensions.
    "E",   # pycodestyle errors.
    "F",   # pyflakes detects broken and unused imports.
    "I",   # isort keeps imports deterministic.
    "UP",  # pyupgrade keeps syntax aligned with supported Python versions.
    "W",   # pycodestyle warnings.
]

```

### frontend/src/main.tsx

```typescript
import {
  MutationCache,
  QueryCache,
  QueryClient,
  QueryClientProvider,
} from "@tanstack/react-query"
import { createRouter, RouterProvider } from "@tanstack/react-router"
import { StrictMode } from "react"
import ReactDOM from "react-dom/client"
import { client } from "./client/client.gen"
import { ThemeProvider } from "./components/theme-provider"
import { Toaster } from "./components/ui/sonner"
import "./index.css"
import { ApiError, createApiError } from "./lib/api-error"
import { buildLoginHref } from "./lib/auth-return"
import {
  clearAuthSession,
  getAccessToken,
  hasAccessToken,
  shouldInvalidateSession,
} from "./lib/session"
import { routeTree } from "./routeTree.gen"

client.setConfig({
  auth: () => getAccessToken() ?? undefined,
  baseUrl: import.meta.env.VITE_API_URL,
})
client.interceptors.error.use(createApiError)

const handleApiError = (error: Error) => {
  if (
    error instanceof ApiError &&
    hasAccessToken() &&
    shouldInvalidateSession(error)
  ) {
    clearAuthSession(queryClient)
    window.location.href = buildLoginHref(
      `${window.location.pathname}${window.location.search}${window.location.hash}`,
    )
  }
}
const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: handleApiError,
  }),
  mutationCache: new MutationCache({
    onError: handleApiError,
  }),
})

const router = createRouter({
  routeTree,
  context: { queryClient },
})
declare module "@tanstack/react-router" {
  interface Register {
    router: typeof router
  }
}

export type RouterContext = {
  queryClient: QueryClient
}

ReactDOM.createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
      <QueryClientProvider client={queryClient}>
        <RouterProvider router={router} />
        <Toaster richColors closeButton />
      </QueryClientProvider>
    </ThemeProvider>
  </StrictMode>,
)

```

### backend/app/main.py

```python
from collections.abc import AsyncIterator, Callable
from contextlib import asynccontextmanager

import sentry_sdk
from fastapi import FastAPI
from fastapi.routing import APIRoute
from starlette.middleware.cors import CORSMiddleware
from txt2crs.application import Txt2CrsApplication
from txt2crs.jobs import ExecutionProfile

from app.api.main import api_router
from app.core.config import Settings, settings
from app.core.exception_handlers import register_exception_handlers
from app.core.logging import setup_logging
from app.core.middleware import RequestLoggingMiddleware, UploadBodyLimitMiddleware
from app.core.rate_limit import limiter
from app.core.telemetry import instrument_app, setup_telemetry
from app.services import (
    CachedReadinessCoordinator,
    RuntimeOwnershipCoordinator,
    SerialTxt2CrsWorker,
    SystemAuthenticationCoordinator,
    Txt2CrsApplicationLifecycle,
    Txt2CrsSubmissionService,
    build_execution_profile,
)

# Configure structured logging based on environment
log_format = "text" if settings.ENVIRONMENT == "local" else "json"
setup_logging(level="INFO", format_type=log_format)

# Initialize OpenTelemetry tracing (must be before creating FastAPI app)
# This enables distributed tracing when OTEL_ENABLED=true and OTLP_ENDPOINT is set
setup_telemetry()


def custom_generate_unique_id(route: APIRoute) -> str:
    return f"{route.tags[0]}-{route.name}"


if settings.SENTRY_DSN and settings.ENVIRONMENT != "local":
    sentry_sdk.init(dsn=str(settings.SENTRY_DSN), enable_tracing=True)

Txt2CrsLifecycleFactory = Callable[[Settings], Txt2CrsApplicationLifecycle]
Txt2CrsWorkerFactory = Callable[
    [Txt2CrsApplication, Settings, RuntimeOwnershipCoordinator],
    SerialTxt2CrsWorker,
]
Txt2CrsReadinessFactory = Callable[
    [
        Txt2CrsApplication | None,
        SerialTxt2CrsWorker | None,
        RuntimeOwnershipCoordinator,
        Settings,
    ],
    CachedReadinessCoordinator,
]
Txt2CrsAuthenticationFactory = Callable[
    [
        Txt2CrsApplication | None,
        RuntimeOwnershipCoordinator,
        Settings,
    ],
    SystemAuthenticationCoordinator,
]
Txt2CrsExecutionProfileFactory = Callable[[Settings], ExecutionProfile]

# Multipart boundaries and headers are counted in the HTTP request body but
# are not part of the learner file. This finite allowance lets one exact-limit
# file plus bounded metadata pass while keeping parser overhead constrained.
UPLOAD_MULTIPART_FRAMING_ALLOWANCE_BYTES = 65_536


def build_txt2crs_lifecycle(
    application_settings: Settings,
) -> Txt2CrsApplicationLifecycle:
    """Build one shell lifecycle owner for one FastAPI lifespan."""

    return Txt2CrsApplicationLifecycle(settings=application_settings)


def build_txt2crs_worker(
    application: Txt2CrsApplication,
    application_settings: Settings,
    runtime_ownership: RuntimeOwnershipCoordinator,
) -> SerialTxt2CrsWorker:
    """Build the one-process serial worker from finite shell settings."""

    return SerialTxt2CrsWorker(
        application=application,
        poll_interval_seconds=application_settings.TXT2CRS_WORKER_POLL_SECONDS,
        heartbeat_interval_seconds=(
            application_settings.TXT2CRS_WORKER_HEARTBEAT_SECONDS
        ),
        shutdown_timeout_seconds=(
            application_settings.TXT2CRS_WORKER_SHUTDOWN_TIMEOUT_SECONDS
        ),
        runtime_ownership=runtime_ownership,
    )


def build_txt2crs_readiness(
    application: Txt2CrsApplication | None,
    worker: SerialTxt2CrsWorker | None,
    runtime_ownership: RuntimeOwnershipCoordinator,
    application_settings: Settings,
) -> CachedReadinessCoordinator:
    """Build one cache over package and safe worker state."""

    return CachedReadinessCoordinator(
        application=application,
        worker=worker,
        runtime_ownership=runtime_ownership,
        refresh_interval_seconds=(
            application_settings.TXT2CRS_READINESS_REFRESH_SECONDS
        ),
        stale_after_seconds=(
            application_settings.TXT2CRS_READINESS_STALE_AFTER_SECONDS
        ),
        shutdown_timeout_seconds=(
            application_settings.TXT2CRS_READINESS_SHUTDOWN_TIMEOUT_SECONDS
        ),
        configured_model_id=application_settings.TXT2CRS_MODEL_ID,
    )


def build_txt2crs_authentication(
    application: Txt2CrsApplication | None,
    runtime_ownership: RuntimeOwnershipCoordinator,
    application_settings: Settings,
) -> SystemAuthenticationCoordinator:
    """Build one cached system-auth owner over the public facade."""

    return SystemAuthenticationCoordinator(
        application=application,
        runtime_ownership=runtime_ownership,
        monitor_poll_seconds=(application_settings.TXT2CRS_AUTH_MONITOR_POLL_SECONDS),
        shutdown_timeout_seconds=(
            application_settings.TXT2CRS_AUTH_SHUTDOWN_TIMEOUT_SECONDS
        ),
    )


def create_app(
    *,
    application_settings: Settings = settings,
    txt2crs_lifecycle_factory: Txt2CrsLifecycleFactory = (build_txt2crs_lifecycle),
    txt2crs_worker_factory: Txt2CrsWorkerFactory = build_txt2crs_worker,
    txt2crs_readiness_factory: Txt2CrsReadinessFactory = (build_txt2crs_readiness),
    txt2crs_authentication_factory: Txt2CrsAuthenticationFactory = (
        build_txt2crs_authentication
    ),
    txt2crs_execution_profile_factory: Txt2CrsExecutionProfileFactory = (
        build_execution_profile
    ),
) -> FastAPI:
    """
    Construct a FastAPI application with an injectable engine lifecycle.

    Tests inject a recording lifecycle and never need Tavily or Codex. The
    exported production app uses the same function, which keeps middleware,
    routes, observability, and exception handlers identical in both paths.
    """

    @asynccontextmanager
    async def application_lifespan(
        fastapi_app: FastAPI,
    ) -> AsyncIterator[None]:
        # Create a fresh owner for every lifespan re-entry. Storing only the
        # shell service (not config, paths, or secrets) gi
[truncated — 6326 more characters]
```

[739 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]