language-learning-app/api/frontend-todo.md
wilson 45336277df
Some checks failed
/ test (push) Has been cancelled
feat: [frontend] Refactor the packs admin interface
2026-04-17 09:39:42 +01:00

8.9 KiB

Frontend TODO


Screen 1 — User: Manual Flashcard Creator

Route: /flashcards/new (and /flashcards/:id/edit for editing)

Purpose: Allow a user to create a flashcard for a word they want to learn, with optional dictionary linking to anchor it to a specific sense.

Layout

A single-page form divided into two sections:

Section A — Word lookup

  • Text input: "Word or phrase" (surface_text). As the user types (debounced ~400ms), call GET /api/dictionary/wordforms?lang_code={target_lang}&text={input} and display results inline.
  • Dictionary search results display as a list of candidate cards. Each card shows:
    • Lemma headword (bold), POS label (e.g. "verb", "noun"), gender if present (e.g. "m." / "f.")
    • Indented list of senses, each showing: sense index, gloss (= English translation), topics/tags as small chips
  • User clicks a sense to select it. Selected state: the sense is highlighted, the sense id is stored, and Section B is pre-populated.
  • If no results are found, show a "No dictionary match — you can still create a card manually" message. The form remains usable without a sense link.
  • "Clear" button to deselect and start over.

Section B — Card content

Four text inputs, pre-populated from the selected sense but always editable:

Field Pre-populated from Label shown to user
prompt_text lemma.headword (if sense selected, else blank) Prompt (target language)
answer_text sense.gloss (if sense selected, else blank) Answer (English)
prompt_context_text blank Context for prompt (optional)
answer_context_text blank Context for answer (optional)

Card direction selector: two toggle options — Recognition (target → English) and Production (English → target). Defaults to both selected (generates two cards). User can deselect one.

Save action:

  1. POST /api/vocab with { surface_text, language_pair_id, entry_pathway: "manual" } → returns a WordBankEntry with a bank_entry_id and disambiguation_status.
  2. If a sense was selected and disambiguation_status != "auto_resolved": PATCH /api/vocab/{entry_id}/sense with { sense_id }.
  3. POST /api/vocab/{entry_id}/flashcards with { direction } for each selected direction.
  4. On success: navigate to the flashcard list or show a confirmation with a "Study now" shortcut.

Edit mode (/flashcards/:id/edit):

  • Pre-populate all fields from the existing flashcard record.
  • Sense search is pre-filled with the existing surface_text and the linked sense highlighted (if present).
  • Save updates the flashcard. (Note: a PATCH /api/flashcards/:id endpoint does not yet exist — this needs to be added to the API.)

State notes

  • language_pair_id must be known before this screen renders. Resolve it from the user's active language pair (stored in app state / from GET /api/learnable-languages).
  • A user may have no dictionary match but still create a valid card manually. Do not block submission if the sense search returns nothing.

Screen 2 — Admin: WordBankPack List

Route: /admin/packs

Auth: Admin token required. All calls go to /api/admin/packs/*.

Purpose: Entry point to the pack CMS. Shows all packs (published and draft) and allows creation of new ones.

Layout

  • Page header: "Word Packs" + "New Pack" button (opens Screen 3).
  • Table with columns: Name (source lang), Name (target lang), Language pair, Proficiencies, Entries, Status (Published / Draft), Actions (Edit, Publish).
  • "Publish" action: POST /api/admin/packs/{id}/publish. Disabled if pack is already published. Show a confirmation modal before calling — publishing is not reversible via the API.
  • Row click → navigate to Screen 3 (edit mode).

Fetch: GET /api/admin/packs?source_lang={}&target_lang={} (filter controls optional).


Screen 3 — Admin: WordBankPack Detail / Editor

Route: /admin/packs/new and /admin/packs/:id

Purpose: Create or edit a pack, manage its entries, and add flashcard templates to each entry.

Layout

Three vertical sections on the same page:


Section A — Pack metadata

Fields mapping directly to CreatePackRequest / UpdatePackRequest:

Field Input type Notes
name text Pack name in source language (English)
name_target text Pack name in target language (e.g. French)
description textarea Description in source language
description_target textarea Description in target language
source_lang select (ISO 639-1) Disabled after creation
target_lang select (ISO 639-1) Disabled after creation
proficiencies multi-select CEFR values: A1, A2, B1, B2, C1, C2

Save: POST /api/admin/packs (new) or PATCH /api/admin/packs/{id} (edit). After creation, the page transitions to edit mode with the new pack ID in the URL.


Section B — Pack entries

A table/list of the pack's word entries. Each row expands to show flashcard templates (Screen 3B).

Adding an entry:

Inline form above the list (always visible):

  • Text input: "Word or phrase" (surface_text). As the user types, call GET /api/dictionary/wordforms?lang_code={target_lang}&text={input} and display results in a dropdown.
  • Dropdown shows: headword + POS + gender + each sense's gloss. User selects a specific sense.
  • Selected state shows a summary pill: e.g. "aller (verb) — to go". Clear button to deselect.
  • "Add entry" button → POST /api/admin/packs/{id}/entries with { surface_text, sense_id }.
    • sense_id is included if a sense was selected; omitted otherwise (entry is created without a sense link — this is valid but means no flashcards can be generated from it until a sense is linked).

Entry row (collapsed):

  • Surface text (bold)
  • Sense gloss if linked, or a "⚠ No sense linked" warning badge
  • Template count (e.g. "2 templates")
  • Delete button → DELETE /api/admin/packs/{id}/entries/{entry_id} with confirmation.
  • Expand toggle.

Entry row (expanded) — Section 3B:

Shows the flashcard template sub-list for this entry. See Section C below.


Section C — Flashcard templates (per entry)

Rendered inside the expanded entry row.

A flashcard template defines the canonical prompt/answer for this word when a user adopts the pack. Fields map to AddFlashcardTemplateRequest:

Field Input type Notes
card_direction select target_to_source (Recognition) / source_to_target (Production)
prompt_text text Pre-populated from sense: headword for target_to_source, gloss for source_to_target
answer_text text Opposite of prompt
prompt_context_text text Optional — example sentence or grammatical cue
answer_context_text text Optional — corresponding target-language context

"Add template" button → POST /api/admin/packs/{id}/entries/{entry_id}/flashcards.

Existing templates are listed below the form, each showing all four fields read-only, with a delete button → DELETE /api/admin/packs/{id}/entries/{entry_id}/flashcards/{template_id}.

(Note: there is no PATCH endpoint for templates — delete and re-create to edit.)

Pre-population hint for admins: When a sense is linked to the entry, the "Add template" form should auto-fill prompt_text and answer_text based on the selected card_direction:

  • target_to_source: prompt = lemma.headword, answer = sense.gloss
  • source_to_target: prompt = sense.gloss, answer = lemma.headword

These are editable before submitting.


API reference summary

Endpoint Used by
GET /api/dictionary/wordforms?lang_code=&text= Screen 1 (live search), Screen 3 (entry add)
POST /api/vocab Screen 1 (save)
PATCH /api/vocab/{id}/sense Screen 1 (save, when sense selected)
POST /api/vocab/{id}/flashcards Screen 1 (save)
GET /api/admin/packs Screen 2
POST /api/admin/packs Screen 3 (new pack)
GET /api/admin/packs/{id} Screen 3 (edit pack)
PATCH /api/admin/packs/{id} Screen 3 (update metadata)
POST /api/admin/packs/{id}/publish Screen 2
POST /api/admin/packs/{id}/entries Screen 3 (add entry)
DELETE /api/admin/packs/{id}/entries/{entry_id} Screen 3 (remove entry)
POST /api/admin/packs/{id}/entries/{entry_id}/flashcards Screen 3 (add template)
DELETE /api/admin/packs/{id}/entries/{entry_id}/flashcards/{template_id} Screen 3 (remove template)

API gaps (need to be added before frontend is complete)

  • PATCH /api/flashcards/{id} — update prompt/answer/context text on an existing user flashcard (needed for Screen 1 edit mode)
  • GET /api/flashcards/{id} — fetch a single flashcard by ID (needed to pre-populate Screen 1 edit mode)
  • GET /api/dictionary/lemmas?lang_code=&headword= or similar — a headword-level search returning all senses for a lemma directly, useful as a fallback when the wordform search returns no results but the user typed a known headword