A moment is more than its words
A translation app takes a sentence and hands one back. Spark keeps a moment: when it happened, where, in what situation, and what Dad saw. I read the words. The moment around them is what lets me read them well, and what lets a later turn find them again. This track follows one photo from the phone to my answer, and it starts before any text is read, with the facts the phone records at the second Dad presses the shutter.
One rule governs those facts. The client tells the truth about the capture, and the engine never invents it. The web client's moments.ts opens with that rule as a comment: the client supplies capture time, timezone and local date, and the engine never takes them from its own clock.
Three facts about time
Every moment carries captured_at, an ISO 8601 instant with the device's own offset; timezone, the IANA zone name; and local_date, the calendar day where Dad stood. The offset and the zone are both kept on purpose. The offset makes the instant exact. The zone name is what a person reads as a place in time.
The phone adds one refinement, because a photo carries a clock of its own. When a photo is prepared, PreparedPhoto.takenAt reads the original's EXIF DateTimeOriginal and OffsetTimeOriginal, and returns an instant only when both are there. AppModel.truth then decides (the second code block). A photo with its own instant keeps it, so a picture picked from the library an hour after it was taken keeps the hour it was taken. A photo without one takes the capture truth its caller supplied: a photo shared into Spark with no EXIF time takes the moment it was shared. Only when nobody supplied one is the time now. A camera still arrives with no metadata, so it takes the time now, and the phone writes that time back into both the original and my copy.
One catch sits in that code. The zone name it records is the phone's current zone, whatever offset the EXIF carried. A photo taken under another offset therefore keeps its own offset beside today's zone name. Lesson 5 shows where that surfaces.
The web is simpler. It keeps a photo with captureTruth(), which is the browser's clock at the moment Dad chooses the file. It does not read the file's EXIF for the moment's time.
The engine adds one stamp of its own, received_at, when a moment reaches it. That is a different fact: when Spark heard of the moment, never when it happened. Lesson 5 shows why keeping the two apart matters.
Where, sampled once
PlaceSampler takes one foreground sample, at hundred-metre accuracy, and only while a capture surface is open. It asks for when-in-use permission once, when that surface opens. It waits four seconds at most for a fix, and no fix in that time means the moment has no place. The ceiling covers the fix and nothing after it. Once a fix arrives, the sampler still awaits the reverse geocoding of its name, which has no timeout of its own, and the capture that asked for the place waits with it. On a slow link that second wait is the network's to decide.
The place name is resolved on the device by reverse geocoding, and the city is appended when the name does not already contain it. A street name alone does not say which town a moment belongs to. The coordinates travel to the engine as the moment's location, the name as place_name.
After the moment is recorded, the engine forwards the fix to cwkPippa's location layer, so my location tool knows where Dad meets a language. It forwards only a whole fix (a position, a precision and a time), only on the first create, and never as a gate: a failed forward costs the moment nothing. My instructions for every Spark turn say the rest: name places, never coordinates.
The situation
A moment also carries a situation: general, restaurant, station, pharmacy, hotel desk, shop or taxi. On the phone these are chips, and the last one Dad chose becomes the next capture's default. The web starts at general and asks. The architecture describes the situation as suggested from the place; the code does not do that today. The phone remembers Dad's last pick, and that is all.
The situation does two jobs later in this track. It rides into my instructions beside the place name, and it selects which earlier results come along as context.
Input modes and who handles each
| Input | On the client | In the engine |
|---|---|---|
| Photo on the phone (camera or library) | Vision locates lines; the phone makes my 2048 px copy | a practical turn in the moment's thread |
| Photo on the web | the original goes up | the engine makes my copy, then the same practical turn |
| Typed or shared text | kept as the moment's text | a practical turn that quotes the text |
| Text for high quality | kept without a reading | versions later (Track 7) |
| Live camera, phone only | the scanner | the live lane (Track 4) |
| Dad's words to say, their reply | Talk and the reply mic | cards and relays (Track 5) |
Every path starts the same way: the moment is kept first, and only then does anyone ask me. Pippa Go Quest teaches that capture-first shape for its own client.
Language: who decides what
- Text languages are my reading. Every source-bearing result I return carries
language, the BCP-47 tag of its source text, andtranslation_language. A sign in two languages gets a different tag on each line. Vision detects language only to read the glyphs; it decides nothing. - Speech languages are chosen. The scene language comes from chips, ten of them (es, ca, eu, gl, pt, fr, it, en, ja, zh). The phone remembers the last one, with Spanish as the first default; the web starts at es. The engine refuses a listening language that is not a short code. The one detected exception is the show card's interpreter, in Track 6.
- The target language is Dad's. Korean, unless he picks another.