lib.rsannotatedlib.rssource722 lines · 28.0 KB · raw

The archive: every request lmjtfy has had answered, and the questions people asked. These are the messages the Worker and the archive's Durable Object exchange. No I/O and no clock.

The rule (the user, 2026-10-02): the same request is never sent to the same model twice. A request is the exact body, and it is kept with its response under the model it went to. Asking again returns that response. The archive is also what makes the call, so two visitors asking the same new thing at once share one request.

10#![forbid(unsafe_code)]
12use ask::{Sent, Wanted};
13use serde::{Deserialize, Serialize};
14
15pub mod event;
16pub use event::{Event, Origin, Seen as Report};

What the Worker asks the archive.

19#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
20pub enum Ask {

Jev's answer to wanted about input. request is the body the Worker prepared and shows on the page; the archive prepares it again from wanted, and refuses if the two differ.

24    Jev { input: String, wanted: Wanted, request: String, #[serde(default)] pick: Pick },

The LLM's reply to request, from model.

26    Llm { model: String, request: String, #[serde(default)] pick: Pick },

input was asked and got answers, one per question Jev answered. llm is whether an LLM had to be asked; listed is whether the question may be shown on the public feed. who is the asking browser for this question only (Asker): a browser that asked it before is not counted again. None when the browser sent no id. browser is that browser's id itself, kept beside who so the owner can see whose row it is.

34    Asked {
35        input: String,
36        answers: Vec<Answer>,
37        llm: bool,
38        listed: bool,
39        #[serde(default)]
40        who: Option<Asker>,
41        #[serde(default)]
42        browser: String,
43    },

Someone cloned or pulled repo (lmjtfy.git) through the site. Counted, and told to every open page.

46    Fetched { repo: String, fetch: Fetch },

who thinks Jev's answer to input right (Up) or wrong (Down). The vote is for the answer as it is kept now; the same vote again takes it back. Answered with the Rating after it.

50    Rate {
51        input: String,
52        who: Asker,
53        vote: Vote,
54        #[serde(default)]
55        browser: String,
56    },

The votes on Jev's answer to input as it is kept now, and who's.

58    Rating { input: String, who: Option<Asker> },

who says more about the vote they have on Jev's answer to input (comment is already clean_commented). Kept beside the vote, as a row of its own that is never changed; answered with Commented.

62    Comment {
63        input: String,
64        who: Asker,
65        browser: String,
66        comment: String,
67    },

What the home page shows from the archive.

69    Home,

The next FEED listed questions asked before after, newest first: the feed scrolled to its end.

72    Older { after: Cursor },

What Jev said to input, if it was ever asked and answered. Nothing is sent to find out.

75    Answer { input: String },

input was a question Jev cannot take (the gate said it is not one, or no question could be made of it). Kept apart from what was answered: it is not counted, listed or toasted, and exists so a link to it can say so without asking anyone.

80    Declined { input: String },

Whether input was declined and never answered. Sends nothing.

82    WasDeclined { input: String },

The questions on the feed that what has been typed so far could be the start of. It reads the archive and asks nobody anything.

85    Suggest { typed: String },

Something happened, to be kept whole (event.rs).

87    Event(Box<Event>),
88}

The archive object's name: there is one, for the whole site. A Worker that binds the archive (the site's, the admin's) reaches it by this.

92pub const OBJECT: &str = "everything";

The path of the archive's door for the owner's admin backend. The site's own Worker never sends a request there; the admin's Worker, which binds the same object from the owner's account, does.

97pub const ADMIN: &str = "/admin";

What the admin backend asks the archive.

100#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
101pub enum Admin {

Rows of the archive's tables: one statement that only reads (reads_only), with a value for each ?.

104    Select {
105        sql: String,
106        #[serde(default)]
107        values: Vec<serde_json::Value>,
108    },

The owner's say on whether input may be shown on the feed: yes, no, or None to leave it to the rules again. Jev's own verdict (asked.listed) is kept beside it, untouched.

112    Moderate { input: String, listed: Option<bool> },

The name of a moment in the archive's last thirty days, to restore to: at_ms, or now. Asking changes nothing.

115    Bookmark { at_ms: Option<f64> },

Puts the whole archive back as it was at bookmark, losing all that was kept since. Answered with the bookmark of the moment just before, which undoes it.

119    Restore { bookmark: String },
120}

What the archive answers the admin backend.

123#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
124pub enum Answered {
125    Rows {
126        columns: Vec<String>,
127        rows: Vec<Vec<serde_json::Value>>,

How many rows SQLite read to answer, which is what Cloudflare meters: a statement that returns ten rows may have read every row of a table to find them.

131        #[serde(default)]
132        read: u64,
133    },
134    Done,
135    Refused(String),

A moment in the archive's history, by name.

137    Bookmark(String),
138}

Whether sql is one statement that can only read. The admin's door changes the archive through Admin's own messages, never through SQL: the schema changes only by a migration, and a response that was kept is never edited. Text in quotes is not looked at, so a question that says "delete" can still be searched for; pass such text as a value.

145pub fn reads_only(sql: &str) -> bool {
146    let mut bare = String::new();
147    let mut quoted = None;
148    for c in sql.chars() {
149        match quoted {
150            Some(quote) if c == quote => quoted = None,
151            Some(_) => {}
152            None if c == '\'' || c == '"' => quoted = Some(c),
153            None => bare.push(c.to_ascii_lowercase()),
154        }
155    }
156    let bare = bare.trim().trim_end_matches(';').trim_end();
157    let words: Vec<&str> = bare.split(|c: char| !c.is_ascii_alphanumeric() && c != '_').filter(|word| !word.is_empty()).collect();
158    const WRITES: [&str; 12] = ["insert", "update", "delete", "drop", "alter", "create", "attach", "detach", "pragma", "vacuum", "reindex", "analyze"];
159    // `replace` is a statement that writes (`REPLACE INTO`) and a function
160    // that does not (`replace(path, '/', ' ')`): only the function is
161    // followed by its arguments.
162    let replaces = bare.match_indices("replace").any(|(at, word)| {
163        let alone = !bare[..at].ends_with(|c: char| c.is_ascii_alphanumeric() || c == '_');
164        let after = &bare[at + word.len()..];
165        alone && !after.starts_with(|c: char| c.is_ascii_alphanumeric() || c == '_') && !after.trim_start().starts_with('(')
166    });
167    quoted.is_none() && !bare.contains(';') && matches!(words.first(), Some(&"select") | Some(&"with")) && !replaces && !words.iter().any(|word| WRITES.contains(word))
168}

Which of a request's kept responses an ask wants. A request is sent again only when a visitor asks for that (Fresh, the page's ↻); every response it ever got is kept, numbered from 1, newest last.

173#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
174pub enum Pick {

The newest kept, or sent if there is none.

176    #[default]
177    Latest,

Sent now, whatever is kept, and kept as the newest.

179    Fresh,

That one, if kept; the newest kept otherwise.

181    Version(u32),

The newest kept, and never sent: Called::NotKept if there is none. What a page that is stepping through old versions uses for the calls after the one it stepped, so looking costs nothing.

185    KeptOnly,
186}

Which version of each call a page wants, by call_id: the page's $pins signal, id:3;id:fresh, later entries winning. A call it does not name gets the newest kept.

191#[derive(Clone, Debug, Default, PartialEq, Eq)]
192pub struct Pins(pub Vec<(String, Pick)>);
194impl Pins {
195    pub fn parse(text: &str) -> Pins {
196        let mut pins: Vec<(String, Pick)> = Vec::new();
197        for entry in text.split(';') {
198            let Some((id, what)) = entry.trim().split_once(':') else { continue };
199            if id.len() != 12 || !id.bytes().all(|b| b.is_ascii_hexdigit()) {
200                continue;
201            }
202            let pick = match what {
203                "fresh" => Pick::Fresh,
204                number => match number.parse::<u32>() {
205                    Ok(version) if version > 0 => Pick::Version(version),
206                    _ => continue,
207                },
208            };
209            pins.retain(|(seen, _)| seen != id);
210            pins.push((id.to_owned(), pick));
211        }
212        Pins(pins)
213    }

Stepping through old versions, and asking nothing new: a version is pinned and nothing is to be sent again. Then a call that is not pinned is only read, never sent, so looking costs nothing.

218    pub fn browsing(&self) -> bool {
219        self.0.iter().any(|(_, pick)| matches!(pick, Pick::Version(_))) && !self.0.iter().any(|(_, pick)| *pick == Pick::Fresh)
220    }

What to ask the archive for, for the call id.

223    pub fn pick(&self, id: &str) -> Pick {
224        match self.0.iter().find(|(seen, _)| seen == id) {
225            Some((_, pick)) => *pick,
226            None if self.browsing() => Pick::KeptOnly,
227            None => Pick::Latest,
228        }
229    }
230}

A call, as the page names it for ↻ and ◀ ▶: the first 12 hex digits of the SHA-256 of where it went and what was sent.

234pub fn call_id(sent_to: &str, request: &str) -> String {
235    use sha2::{Digest, Sha256};
236    let digest = Sha256::new().chain_update(sent_to.as_bytes()).chain_update([0]).chain_update(request.as_bytes()).finalize();
237    digest.iter().take(6).map(|byte| format!("{byte:02x}")).collect()
238}

A response the archive holds, and the call that got it.

241#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
242pub struct Record {

The response body exactly as it arrived.

244    pub response: String,
245    pub request_id: Option<String>,
246    pub attempts: u32,

The round trip of the call that was sent, in milliseconds.

248    pub took_ms: f64,

When that call was answered, in Unix milliseconds.

250    pub answered_ms: f64,

Whether this ask is the one that sent it.

252    pub sent_now: bool,

Which of the request's kept responses this is, from 1, and how many there are.

255    #[serde(default = "first")]
256    pub version: u32,
257    #[serde(default = "first")]
258    pub versions: u32,
259}
261fn first() -> u32 {
262    1
263}
264
265impl Record {
266    pub fn sent(&self) -> Sent {
267        if self.sent_now { Sent::Now } else { Sent::Before { at_ms: self.answered_ms } }
268    }

The same record, as someone who did not send it sees it.

271    pub fn kept(mut self) -> Self {
272        self.sent_now = false;
273        self
274    }
275}

A browser, for one question: the hex SHA-256 of the browser's random id and the question. It says whether this browser asked this question before, and nothing else: two questions from one browser give unrelated values, and the id cannot be had back from one.

281#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
282pub struct Asker(pub String);

A browser's view of Jev's answer.

285#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
286pub enum Vote {
287    Up,
288    Down,
289}

The votes on one answer, and the asking browser's own, if it voted.

292#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
293pub struct Rating {
294    pub up: u32,
295    pub down: u32,
296    pub mine: Option<Vote>,

Whether these are votes on Jev declining the question (up: right to decline) and not on an answer (up: right).

299    #[serde(default)]
300    pub declined: bool,
301}

The most a comment on a vote may be, in characters.

304pub const MOST_COMMENT: usize = 1000;

Why a comment was not kept.

307#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
308pub enum Unsaid {

Nothing but whitespace.

310    Empty,

More than MOST_COMMENT characters. Refused, not cut: the visitor keeps what they wrote and shortens it.

313    TooLong,

The browser has no vote on this answer to say more about (or no id).

315    NoVote,

Too many from this visitor this minute.

317    Limit,

The archive could not be asked or could not write it.

319    Failed,
320}

A comment as it is kept: trimmed, line ends made \n, and every other control character dropped. It is the visitor's own words, so it stays untrusted wherever it is shown: this does not make it safe to put in a page, and nothing is done to it for that.

326pub fn clean_comment(raw: &str) -> Result<String, Unsaid> {
327    let text: String = raw.replace("\r\n", "\n").replace('\r', "\n").chars().filter(|c| *c == '\n' || *c == '\t' || !c.is_control()).collect();
328    let text = text.trim();
329    match text.chars().count() {
330        0 => Err(Unsaid::Empty),
331        n if n > MOST_COMMENT => Err(Unsaid::TooLong),
332        _ => Ok(text.to_owned()),
333    }
334}

Which shared budget refused a call.

337#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
338pub enum Pot {
339    Jev,
340    Llm,
341}

What became of a call.

344#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
345pub enum Called {
346    Answered(Record),

Sent, or not sendable, and there is no response. Nothing is kept, so the same request may be sent again.

349    Failed { error: String, request_id: Option<String>, took_ms: f64 },

Not sent: today's budget for it is used up.

351    Spent(Pot),

Not sent, because the ask was Pick::KeptOnly and nothing is kept.

353    NotKept,
354}

What Jev said to one question, as it is kept for the feed and for link previews: the answer in a few words, and the numbers behind it.

358#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
359pub struct Answer {

What the page prints large: "No?", "Pepperoni.", "28%.".

361    pub headline: String,
362    pub detail: Detail,
363}

The numbers behind an answer, by the type of question it answered.

366#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
367pub enum Detail {

Kept before details were. Only the headline is known.

369    Unknown,

A yes-or-no: the probability of yes.

371    Noul { p_yes: f64 },

A how-likely, which Jev answers as a Noul about the thing itself: the probability that it is so.

374    Chance { p: f64 },

A pick: every option with its probability, most likely first.

376    Choice { confidence: f64, options: Vec<(String, f64)> },

A how-much: every level with its probability, lowest first.

378    Score { score: f64, confidence: f64, levels: Vec<(String, f64)> },
379}
381impl Answer {

The type and the numbers in one line, as the page prints them under an answer. None when only the headline was kept.

384    pub fn note(&self) -> Option<String> {
385        Some(match &self.detail {
386            Detail::Unknown => return None,
387            Detail::Noul { p_yes } => format!("Noul · p(yes) = {p_yes:.2}"),
388            Detail::Chance { p } => format!("Noul · p = {p:.2} · Jev thinks that's {}", ask::likelihood(*p)),
389            Detail::Choice { confidence, .. } => format!("Choice · confidence {confidence:.2}"),
390            Detail::Score { score, confidence, levels } => {
391                format!("Score · {score:.1} of {} · confidence {confidence:.2}", levels.len().saturating_sub(1))
392            }
393        })
394    }

The most likeliest options or levels, likeliest first, for a line of text. Empty for a Noul, whose one number is in the note.

398    pub fn spread(&self, most: usize) -> Vec<(&str, f64)> {
399        let mut spread: Vec<(&str, f64)> = match &self.detail {
400            Detail::Choice { options, .. } => options.iter().map(|(label, p)| (label.as_str(), *p)).collect(),
401            Detail::Score { levels, .. } => levels.iter().map(|(label, p)| (label.as_str(), *p)).collect(),
402            _ => Vec::new(),
403        };
404        spread.sort_by(|a, b| b.1.total_cmp(&a.1));
405        spread.truncate(most);
406        spread
407    }

Everything in one line: the headline, the note and the spread.

410    pub fn line(&self) -> String {
411        let mut parts = vec![self.headline.clone()];
412        parts.extend(self.note());
413        let spread: Vec<String> = self.spread(3).iter().map(|(label, p)| format!("{label} {:.0}%", p * 100.0)).collect();
414        if !spread.is_empty() {
415            parts.push(spread.join(", "));
416        }
417        parts.join(" · ")
418    }
419}

A kept answer as it is stored: with its numbers, or, from before they were kept, as the headline alone.

423#[derive(Deserialize)]
424#[serde(untagged)]
425enum Stored {
426    Whole(Answer),
427    Headline(String),
428}

Reads the answers a question was stored with, old form or new.

431pub fn stored(text: &str) -> Vec<Answer> {
432    let stored: Vec<Stored> = serde_json::from_str(text).unwrap_or_default();
433    stored
434        .into_iter()
435        .map(|stored| match stored {
436            Stored::Whole(answer) => answer,
437            Stored::Headline(headline) => Answer { headline, detail: Detail::Unknown },
438        })
439        .collect()
440}

A question somebody asked, and what Jev said. Nothing about who asked.

443#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
444pub struct Entry {
445    pub input: String,
446    pub answers: Vec<Answer>,

When it was last asked.

448    pub asked_ms: f64,

How many times it has been asked and answered.

450    pub times: u32,
451}
453impl Entry {

The answers in a few words each, as the feed prints them.

455    pub fn headlines(&self) -> String {
456        let headlines: Vec<&str> = self.answers.iter().map(|answer| answer.headline.as_str()).collect();
457        headlines.join(" ")
458    }
459}

How many questions are suggested for what has been typed, and how many characters must be typed before any are.

463pub const SUGGESTED: usize = 5;
464pub const SUGGEST_FROM: usize = 3;

Every question the feed may show, ready to be matched against what a visitor is typing. Only those: a suggestion is shown to strangers, as the feed is.

469#[derive(Clone, Debug, Default, PartialEq)]
470pub struct Listed(Vec<(String, Entry)>);
472impl Listed {
473    pub fn new(entries: impl IntoIterator<Item = Entry>) -> Listed {
474        Listed(entries.into_iter().map(|entry| (entry.input.to_lowercase(), entry)).collect())
475    }

The questions typed could be the start of, whatever its case: those that begin with it, then those with a word that begins there, the most asked first, and no more than SUGGESTED. Nothing until SUGGEST_FROM characters are typed.

481    pub fn suggest(&self, typed: &str) -> Vec<Entry> {
482        let typed = typed.trim().to_lowercase();
483        if typed.chars().count() < SUGGEST_FROM {
484            return Vec::new();
485        }
486        let within = format!(" {typed}");
487        let mut found: Vec<(u8, &Entry)> = self
488            .0
489            .iter()
490            .filter_map(|(lower, entry)| if lower.starts_with(&typed) { Some((0, entry)) } else if lower.contains(&within) { Some((1, entry)) } else { None })
491            .collect();
492        found.sort_by(|a, b| a.0.cmp(&b.0).then(b.1.times.cmp(&a.1.times)).then(b.1.asked_ms.total_cmp(&a.1.asked_ms)).then(a.1.input.cmp(&b.1.input)));
493        found.into_iter().take(SUGGESTED).map(|(_, entry)| entry.clone()).collect()
494    }
495}

How many questions the feed shows as asked lately, and as most asked.

498pub const FEED: u32 = 50;
499pub const MOST: u32 = 5;

The archive in numbers.

502#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
503pub struct Stats {

Different questions answered.

505    pub questions: u32,

Times a question was asked and answered, repeats included.

507    pub asks: u32,

Of the different questions, those Jev answered with no LLM.

509    pub no_llm: u32,

Requests that went out and were answered.

511    pub sent: u32,

Requests that did not go out, because their response was kept.

513    pub kept: u32,

Clones and pulls of the code through the site.

515    #[serde(default)]
516    pub clones: u32,
517    #[serde(default)]
518    pub pulls: u32,
519}
521impl Stats {

The share of questions answered with no LLM, as a whole percentage.

523    pub fn no_llm_percent(&self) -> Option<u32> {
524        (self.questions > 0).then(|| (f64::from(self.no_llm) * 100.0 / f64::from(self.questions)).round() as u32)
525    }
526}

What the home page shows from the archive. The lists hold only questions that may be shown publicly; the numbers count every question.

530#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
531pub struct Home {

Newest first.

533    pub lately: Vec<Entry>,

Asked more than once, most asked first.

535    pub most: Vec<Entry>,
536    pub stats: Stats,
537}

Where the feed was scrolled to: the last question shown. Questions are listed newest first, ties broken by the question itself, so the next page starts strictly after this one and none is shown twice or skipped.

542#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
543pub struct Cursor {
544    pub asked_ms: f64,
545    pub input: String,
546}
548impl Entry {

Where the feed is once this entry is the last shown.

550    pub fn cursor(&self) -> Cursor {
551        Cursor { asked_ms: self.asked_ms, input: self.input.clone() }
552    }
553}

What a fetch through the clone proxy was. Git's upload-pack request names the commits wanted; a pull also names the ones it has.

557#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
558pub enum Fetch {
559    Clone,
560    Pull,
561}
563impl Fetch {

Its name in the archive's counts, for one repository.

565    pub fn counted(self, repo: &str) -> String {
566        match self {
567            Fetch::Clone => format!("clone:{repo}"),
568            Fetch::Pull => format!("pull:{repo}"),
569        }
570    }
571}

What the archive pushes to every open page over its socket, as JSON: {"online": 3} or {"toast": "<html>"}.

575#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
576#[serde(rename_all = "lowercase")]
577pub enum Live {

Pages open now.

579    Online(u32),

Something happened, as markup to show for a moment.

581    Toast(String),

The build the site is now (LMJTFY_BUILD), told to each page as it connects. A page built from another offers a reload: a deploy restarts the object, every page reconnects, and each hears this.

585    Build(String),

Elements that changed, each with an id: a page that has an element with that id replaces it. An element with data-prepend="<id>" instead puts its children at the top of that element, each removing any element with its id first: a question asked again moves to the top of the feed, and what a page has scrolled in below stays. The activity feeds stay current this way, with no refresh.

592    Patch(String),
593}

Where an open page connected from, as Cloudflare placed the request: a country code and a city, either unknown. It is kept on the page's socket while it is open, for the online list.

598#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
599pub struct Place {
600    pub country: Option<String>,
601    pub city: Option<String>,
602}
604impl Place {

From what the Worker passed on, kept only if it looks like a place: a country is two letters, a city a short line of text.

607    pub fn new(country: Option<&str>, city: Option<&str>) -> Place {
608        let country = country
609            .map(str::trim)
610            .filter(|code| code.len() == 2 && code.bytes().all(|b| b.is_ascii_alphabetic()))
611            .map(str::to_ascii_uppercase)
612            // Cloudflare's own codes for Tor and for unknown are not places.
613            .filter(|code| code != "T1" && code != "XX");
614        let city = city
615            .map(str::trim)
616            .filter(|city| !city.is_empty() && city.chars().count() <= 64 && !city.chars().any(char::is_control))
617            .map(str::to_owned);
618        Place { country, city }
619    }

The country's flag: its two letters as regional indicator symbols.

622    pub fn flag(&self) -> Option<String> {
623        let code = self.country.as_deref()?;
624        code.chars().map(|letter| char::from_u32(0x1F1E6 + (letter as u32 - 'A' as u32))).collect()
625    }

Austin, US, US, or somewhere.

628    pub fn label(&self) -> String {
629        match (&self.city, &self.country) {
630            (Some(city), Some(country)) => format!("{city}, {country}"),
631            (Some(city), None) => city.clone(),
632            (None, Some(country)) => country.clone(),
633            (None, None) => "somewhere".to_owned(),
634        }
635    }
636}

A header value as the Worker percent-encoded it (+ for a space), back to text. None if it is not UTF-8 or a % is not followed by two hex digits.

641pub fn percent_decoded(text: &str) -> Option<String> {
642    let mut bytes = Vec::with_capacity(text.len());
643    let mut rest = text.bytes();
644    while let Some(byte) = rest.next() {
645        match byte {
646            b'+' => bytes.push(b' '),
647            b'%' => {
648                let hex = [rest.next()?, rest.next()?];
649                bytes.push(u8::from_str_radix(std::str::from_utf8(&hex).ok()?, 16).ok()?);
650            }
651            byte => bytes.push(byte),
652        }
653    }
654    String::from_utf8(bytes).ok()
655}

What the archive keeps on an open page's socket: where it is, and whether a Worker that passes places on sent it. A page that reconnects while a deploy is still reaching Cloudflare's edge can come through the Worker from before, which passes no place; passed is false for it, and the archive asks it to reconnect once the deploy has settled.

662#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
663pub struct Seen {
664    pub place: Place,
665    pub passed: bool,

When it connected, in Unix milliseconds.

667    pub at_ms: f64,

The row of events that says it connected, which the row that says it left copies who it was from.

670    #[serde(default)]
671    pub event: Option<f64>,
672}

How long after connecting an unplaced page is asked to reconnect: long enough for a deploy to reach the whole edge.

676pub const REPLACE_AFTER_MS: f64 = 10_000.0;
678impl Seen {

Whether this page should reconnect to be placed: it came through a Worker that passes no place, long enough ago. A socket with nothing kept on it (None) was accepted by code from before places.

682    pub fn stale(seen: Option<&Seen>, now_ms: f64) -> bool {
683        seen.is_none_or(|seen| !seen.passed && now_ms - seen.at_ms >= REPLACE_AFTER_MS)
684    }
685}

How many open pages are in each place, most first, then by name.

688pub fn places(open: impl IntoIterator<Item = Place>) -> Vec<(Place, u32)> {
689    let mut counted: std::collections::BTreeMap<Place, u32> = std::collections::BTreeMap::new();
690    for place in open {
691        *counted.entry(place).or_default() += 1;
692    }
693    let mut counted: Vec<(Place, u32)> = counted.into_iter().collect();
694    counted.sort_by(|(a, n), (b, m)| m.cmp(n).then_with(|| a.label().cmp(&b.label())));
695    counted
696}

What the archive says back.

699#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
700pub enum Told {
701    Called(Called),
702    Noted,
703    Home(Home),
704    Answer(Option<Entry>),
705    WasDeclined(bool),
706    Older(Vec<Entry>),
707    Suggested(Vec<Entry>),

None if the question was never answered, so there is nothing to vote on.

710    Rating(Option<Rating>),

What became of a Comment.

712    Commented(Result<(), Unsaid>),
713}

A moment as YYYY-MM-DD HH:MM UTC.

716pub fn when(unix_ms: f64) -> String {
717    let minutes = (unix_ms / 60_000.0).floor().max(0.0) as u64;
718    format!("{} {:02}:{:02} UTC", budget::date(budget::day(unix_ms)), minutes / 60 % 24, minutes % 60)
719}
721#[cfg(test)]
722mod tests;