# Custom Text Upload (My Texts)

Feature introduced in issue #30. Lets Pro and Instructor subscribers upload their own Latin/Greek texts and read them with full morphological parsing and SRS integration.

## Access

| Tier | Access | Quota |
|------|--------|-------|
| Free | Upsell shown, upload blocked | — |
| Pro | Full access | 20 texts, 50,000 chars each |
| Instructor | Full access | 100 texts, 50,000 chars each |
| Academic | Full access | 100 texts, 50,000 chars each |

## Navigation

**My Texts is not in the main nav bar.** It's accessible via a small link at the bottom of the Today page, next to "View your progress". The link only renders for Pro, Instructor, and Academic accounts. Free users see an upsell block if they navigate directly to `/#/my-texts`.

This keeps the navbar uncluttered for the core workflow (Today → Review → Analyze).

## How It Works

1. Scroll to the bottom of the **Today** page and click **My Texts**.
2. Click **Upload a text**, fill in title, optional author, language (Latin or Greek), and paste the raw text.
3. Acknowledge the ToS checkbox: *"I am uploading text I have the right to use for personal study."*
4. Submit. The text is stored privately — only the uploading user can access it.
5. Click any text in the list to open it in the full reader.
6. In the reader, click any word to parse it (morphology + definitions) and add it to your SRS deck exactly like built-in passages.

## Storage Model

Custom texts are stored in the `custom_texts` table:

```sql
id            text PRIMARY KEY
instructor_id text REFERENCES users(id) ON DELETE CASCADE  -- owner (all tiers)
title         text
author        text DEFAULT ''
language      text DEFAULT 'latin'   -- 'latin' | 'greek'
raw_text      text
created_at    timestamptz DEFAULT now()
CHECK (char_length(raw_text) <= 50000)
```

Word occurrences are **not** pre-indexed for custom texts. All morphological analysis happens on-demand via the `/api/parse` endpoint when the user clicks a word in the reader.

## API

All endpoints require authentication. Pro+ tier required for write/list operations.

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/custom-texts` | List caller's texts (Pro+) |
| `POST` | `/api/custom-texts` | Upload a new text (Pro+) |
| `DELETE` | `/api/custom-texts/:id` | Delete own text (Pro+) |
| `GET` | `/api/texts/:id` | Fetch as PassagePayload for reader (owner only) |

### POST /api/custom-texts

Request body:

```json
{
  "title": "De Amicitia, Book I",
  "author": "Cicero",
  "language": "latin",
  "rawText": "Quintus Mucius augur...",
  "tosAcknowledged": true
}
```

Returns `{ "id": "<uuid>" }` on success.

Error codes:
- `400` — missing/invalid fields
- `401` — not authenticated
- `402` — not Pro tier (upgrade required)
- `429` — quota reached

## Read Route

Custom texts use the existing read route with `type=text`:

```
/#/read/text/<id>
```

The `ReadPage` component already handled this route via `/api/texts/:id`. The response is a standard `PassagePayload` with `wordOccurrences: []` (empty — parsed on-demand by the user clicking words).

## Future Extensions (Instructor)

The `assigned_passages` and `custom_text_completions` tables already exist in the schema to support assigning custom texts to students. This is deferred to a later Instructor feature.
