# Flutter Backend Handoff Guide

**Project:** NumoniX / Leemcwest Backend
**Audience:** Flutter developer
**Purpose:** Explain the backend behavior that the Flutter app must consume.

This guide reflects the current backend implementation and the agreed CMS, quiz, sentence-visual, continue-label, and frontend handover requirements.

## 1. Environment and response format

Configure the backend URL by environment:

~~~dart
const apiBaseUrl = 'https://leemcwest-backend.test/api';
~~~

Do not hard-code the test URL in production. Use separate Flutter configuration for local, staging, and production.

Successful responses:

~~~json
{
  "data": {},
  "message": "...",
  "status": 200
}
~~~

Validation or API errors:

~~~json
{
  "message": "...",
  "errors": {},
  "status": 422
}
~~~

Check the HTTP status code first, then parse data only for successful responses.

## 2. Authentication and guest device identity

### Authenticated user

Send the Sanctum token:

~~~http
Authorization: Bearer <token>
Accept: application/json
~~~

### Guest user

On first launch, create or load a stable device ID and stable random token:

~~~http
POST /api/guest/identify
Content-Type: application/json

{
  "device_id": "stable-device-id",
  "token": "stable-random-token"
}
~~~

Store the returned values:

~~~json
{
  "guest_device_id": 12,
  "device_id": "stable-device-id",
  "token": "stable-random-token",
  "guest_fingerprint": "server-generated-value",
  "is_new": true
}
~~~

For guest-aware endpoints send all three headers:

~~~http
X-Guest-Device-Id: stable-device-id
X-Guest-Token: stable-random-token
X-Guest-Fingerprint: server-generated-value
~~~

Never calculate or modify guest_fingerprint in Flutter. Authenticated users do not need guest headers.

## 3. Home screen and Continue Learning

~~~http
GET /api/home
~~~

Read these response paths:

~~~text
data.continue_learning
data.modules
data.greeting
data.headline
~~~

continue_learning can be null or:

~~~json
{
  "label": "CONTINUE LEARNING",
  "learning_id": 2,
  "title": "Learn Lesson",
  "subtitle": "Lesson 1 of 3",
  "progress_percent": 33,
  "accent_color": "#2457E6",
  "module_key": "lesson",
  "continue_route": "/learnings/show/2",
  "learning_title": "The Fixed Numeric Row",
  "learning_slug": "the-fixed-numeric-row",
  "total_pages": 4,
  "all_lessons_route": "/learnings/lessons"
}
~~~

Use label exactly. Do not recreate its logic from progress_percent:

- START LEARNING means no completed or unfinished learning is available.
- CONTINUE LEARNING means the user has progress or an unfinished learning record.

## 4. Learning detail and sentence visuals

~~~http
GET /api/learnings/show/{learningId}
~~~

Learning content is under:

~~~text
data.learning.pages[].elements[].data
~~~

Pages have independent labels:

~~~json
{
  "page_number": 1,
  "title": "...",
  "section_title": "Core Pattern"
}
~~~

Do not assume title and section_title are the same.

### Canonical sentence visual

~~~json
{
  "type": "sentence_visual",
  "title": "Two-Voice T-S-D-T",
  "description": "...",
  "mode": "cells",
  "beat_numbering_mode": "restart_at_8",
  "playback_label": "Play",
  "tap_to_play": true,
  "show_root_toggle": true,
  "show_reframe_labels": true,
  "beats": [
    {"label": "1"},
    {"label": "2"}
  ],
  "rows": [
    {
      "label": "TOP",
      "start_from_cell": 1,
      "cells": [
        {
          "type": "normal",
          "beat": 1,
          "functions": [
            {
              "world": "T",
              "notes": [{"number": "1"}]
            }
          ]
        }
      ]
    }
  ]
}
~~~

Supported cell types:

- normal: normal harmonic cell.
- empty: no functions; render an empty beat.
- pair_v_a: two functions using reframe variant A.
- pair_v_b: two functions using reframe variant B.

Pair example:

~~~json
{
  "type": "pair_v_b",
  "beat": 4,
  "functions": [
    {"world": "T", "reframe_role": "old", "notes": [{"number": "1"}]},
    {"world": "D", "reframe_role": "new", "notes": [{"number": "5"}]}
  ],
  "reframe": {
    "variant": "B",
    "roots": {
      "old": {"degree": "1", "pitch": "C"},
      "new": {"degree": "1", "pitch": "G"}
    }
  },
  "editable": false,
  "allowable_notes": ["1", "3", "5"],
  "centre_voicing": "C4",
  "octave_range": 2
}
~~~

Pair behavior:

- Missing or false editable means read-only.
- true enables learner editing.
- Use allowable_notes, centre_voicing, and octave_range to constrain editing.
- Pair functions must contain exactly one old and one new reframe_role.

### Numbering and temporal offset

Read beat_numbering_mode exactly:

- restart_at_8: restart according to the eight-beat sentence rule.
- continuous: continue numbering across sentence sections.

Older content may contain continuous_numbering; support it as a fallback, but prefer beat_numbering_mode.

rows[].start_from_cell is a 0-based authoring value:

- 0 means no offset and may be omitted from the API.
- Valid non-zero values are 1 through cellCount - 1.
- It is not a learner-editable control.
- Do not treat it as a beat number.

When mode is held, use segments instead of cells. Segments contain beat_from, beat_to, world, notes, and optional root/weight data.

## 5. Quiz integration

### Load a quiz

~~~http
GET /api/quiz/by-learning/{learningId}
~~~

Read data.quiz and data.questions.

Question example:

~~~json
{
  "id": 10,
  "number": 1,
  "type": "mcq",
  "question_text": "...",
  "image_url": "https://...",
  "image_size": "small",
  "options": ["Option A", "Option B"]
}
~~~

Rules:

- image_url is nullable and is a full URL when present.
- image_size is small, medium, or null.
- If an image exists without a saved size, use medium.
- Do not show correct_answer on the initial quiz screen.
- Current question types are mcq and tap_order.

### Submit, review, and retry

~~~http
POST /api/quiz/submit/{learningId}
GET  /api/quiz/review/{attemptId}
GET  /api/quiz/review-by-learning/{learningId}
POST /api/quiz/retry/{learningId}
~~~

Submit body:

~~~json
{
  "answers": [
    {"question_id": 10, "answer": 0},
    {"question_id": 11, "answer": [2, 0, 1]}
  ]
}
~~~

The submit response is under data.result and includes score, accuracy, attempted questions, correct answers, and result actions.

Review responses may include correct_answer, user_answer, is_correct, correct_answer_display, explanation, options, and image fields. Show answer explanations only on review.

## 6. Completion and CMS user guide

Mark learning complete:

~~~http
POST /api/learnings/completed
Content-Type: application/json

{"learning_id": 2}
~~~

User guide:

~~~http
GET /api/cms/user-guide
~~~

Read data.tabs. The harmonic_builder tab can contain tiles with images and keys. Image src values are full URLs. The getting_started tab contains sections and text items.

## 7. Flutter implementation checklist

- Create one API client for the base URL and common headers.
- Persist guest device ID, token, and fingerprint.
- Add models for home, learning, pages, elements, sentence visuals, quizzes, and quiz questions.
- Treat nullable backend fields as nullable Dart fields.
- Use a discriminator for normal, empty, pair, and held-row data.
- Render quiz images only when image_url is non-null.
- Use backend continue_learning.label directly.
- Preserve independent page title and section_title.
- Handle loading, empty, 401/403, 404, 422, and retry states.
- Add widget/golden tests for pair cells, empty cells, quiz images, both numbering modes, offsets, and mobile-width scrolling.
- Never expose quiz answers before submission.

## 8. Verification and remaining Flutter work

The backend currently passes 323 tests and 884 assertions. The admin editor was checked at desktop and mobile widths with no page-level horizontal overflow or console errors.

The Flutter developer still needs to verify:

- Flutter API model and widget integration.
- Frontend merge of the user-guide/manual screen.
- iOS build and screenshot-protection phase.
- Production URL, certificates, and release configuration.

Paddle checkout, founder programme, automated email flows, and app launch are separate future scope and are not required for this backend handoff.
