The feed is a vertical stream of box covers ranked by the planner. It is computed on the device from the cached catalog and local history, so it opens instantly and works offline.
sequenceDiagram
participant U as Learner
participant F as Feed
participant P as Planner
participant DB as IndexedDB
U->>F: open or scroll near the end
F->>DB: seen, keeps, attempts, interests
F->>P: rank(candidates, learner, variant)
P-->>F: next items + decision log
F->>DB: outbox: decision (analytics lane)
F-->>U: covers
U->>F: open a box
F->>DB: outbox: decision outcome
A box opens on its Hook, and each swipe goes one layer deeper: Why, How, Code, Try. Learners can stop at any layer; the deepest layer reached is recorded in seen and drives the layer_reach and box_dropoff views.
| Piece | Implementation |
|---|---|
| Layout and typography | renderer package, design tokens from ui |
| Illustrations | scenes package: vector scenes parameterised by content |
| Interactives (50+) | playables package; each loads on demand the first time a box shows it |
| Code layer | Highlighted snippets, copied as plain text |
| Captions | English or Hinglish, chosen in Me |
decision Interactives are code, their parameters are content. A new box can reuse any interactive with new parameters through a content publish; only a brand-new kind of interactive needs an app release, and older builds hide boxes that need it.
The Try layer asks one question, then a confidence: Guessing, Kinda sure or Bet my chai.
flowchart LR
A[answer] --> C{correct?}
C -- yes --> S[well done · mastery up]
C -- no --> K{confident?}
K -- "Bet my chai" --> CARD[card created automatically<br/>a confident mistake is worth reviewing]
K -- "Guessing / Kinda sure" --> E[explain · move on]
Every attempt is stored with its confidence, which feeds mastery, the try_quality view and the confusing_words analysis.
puzzle_results./s/<code>). The Worker renders a link preview card for messengers; opening it shows the same puzzle and the sender's result.${sch.nav()}