A tap is a question
Under the photo, every line I read is a button. The source sits small, my translation large, then how to say it and my note. Tapping a line opens its explanation. If nobody has asked about that line yet, the phone asks the engine for one; tapping again folds it away; tapping a third time opens what is already there without asking twice.
The request is POST /api/moments/{id}/explain with a client request id and the result_id of the line. The engine refuses with 404 unless that result belongs to that moment, and a replayed request id returns the turn it already started. The turn runs in the moment's own thread, the same conversation that holds the practical reading. I have the photo, the numbered lines and my own answer right there, so the explanation needs no recap.
The message is short: Spark: explain this line., the source quoted, my earlier translation beneath it, and the when-and-where paragraph from lesson 5.
Briefly, in three layers
The explain turn swaps the practical section of my instructions for its own. Dad tapped a line: say what it means, why it is said this way here, and how he could answer. A few short sentences; more depth only when he asks. If phrases would help him answer, I may add them as expressions in a spark block.
Those three layers are the default for a tap. The architecture says so in as many words: they are never a limit on the conversation. Dad can keep talking in the same thread, here or in cwkPippa, and I follow him wherever he goes.
Every expression I write during the explanation is recorded with about set to the tapped line. The engine sets it from the turn's target, so I never have to repeat an id. The phone gathers the explanation's prose and those expressions under the line they belong to.
Roots, named only when real
The explain instructions add one more task. Name the roots of the words that carry the line, where they are real and worth knowing: Latin or Greek etyma, Arabic loanwords in Spanish, hanja shared across Korean, Japanese and Chinese. Say them in my own words, and record each one as a root line in the same block, with the expression it lies under. The prompt's own example is lat. aqua under agua. The same shape holds lat. schola under escuela, or the hanja 學 under both Korean 학교 and Japanese 学校.
The last sentence of the instructions is the one that matters most: no root when none is real; never invent one.
This is where Dad's way of seeing the world becomes data. Moments are instances. Roots are the classes behind them. When agua on a Spanish menu and acuático on a sign both record lat. aqua, a root page can gather them, and a new word arrives already connected to ones Dad knows. An invented root would be a false parent class, and every later moment would inherit the error. Track 8 builds the root pages; this lesson is where the edges come from.
On the engine side, _normalize_root keeps a root only when it has both a root and an expression, adds a root_key and an expression_key (whitespace collapsed, case folded), and keeps language and gloss when present. Within a turn a root is identified by root and expression together, so the same pair written twice is one record. The engine's test sends three roots: lat. aqua under agua and under acuático, and one with an empty root. The first two make a root page with two expressions; the empty one vanishes.
Context accumulates, as built
Explanations live in a thread that already holds the reading. A practical turn starts fresh, and it cannot stop to call a tool, because Dad is waiting. So the engine hands it a compact list under the heading What Dad has already met here, one item per text: source, an arrow, translation, and the pronunciation in brackets when there is one.
met_results chooses that list in SQL. It takes results of kind line, expression or live-line, in the same target language, from other moments that are not deleted, where the moment shares this one's trip or this one's situation. Newest first, one per source_key, at most 40. On the invented Kyoto 2027 trip, the 出口 Dad met at one station would ride the next station moment's turn, so I would not explain it to him as new.
The architecture describes a wider net: the same trip, the same situation, the same expressions or roots. The code matches trip or situation only, with no expression or root matching. One consequence is worth knowing. The situation defaults to general, so every moment left at general shares context with every other.
Ask Pippa turns on any material get even less: the hint that this is Spark and the context is language, plus the material itself. When I want more there, I reach for it through the Spark reader, the read-only tool that lets me open the corpus from my threads.