package chrome import ( "fmt" "strings" "time" ) // LinkLifetime is how long an emailed sign-in link works. It is // keymaildev/signin's LinkTTL default, which every sign-in in the suite // inherits: rastrillo's auth, Home's own flow and the keymail path all // leave LinkTTL unset (checked 2026-10-01 at signin v0.1.1, rastrillo // main 2fafa12 and v0.23.0, and Home 7a0f19c). The copy below says the // number, so a sign-in that changes its lifetime must change this too, or // the door and the email tell people something false. const LinkLifetime = 15 * time.Minute // DoorCopy is the door's words: the sign-in form, the check-your-email // state, the signed-out page and the sign-in email. English is the // suite's one copy set; a locale replaces the whole set through // SignIn.Copy and SignInNotice.Copy, never a word at a time, so no app // can drift from the others by overriding one line. // // The words are the copy deck in §9.2 of the shared-chrome spec // (docs/superpowers/specs/2026-09-23-shared-chrome.md, on estilo's // spec/shared-chrome branch), with Paul's later rulings noted where they // apply. "§9.2" and "§10" below are that document's sections. // // Translations are estilo's follow-up, never an app's. English is the // only set today. When the suite needs another locale, estilo adds its // whole DoorCopy here, beside English, with a way to pick one by locale, // and every app gets it on its next re-pin. An app passes Copy only a set // estilo publishes; it never writes or edits one, or the one copy set // fragments again the first time an app translates its own door. // // Slots are tokens, not Printf verbs: {app} is the app's name, and {link} // (the email body only) is the sign-in link. A translation may drop or // move a slot, or carry a literal "%", and still render as written. type DoorCopy struct { // The form: heading (also the tab title's page), address label, the // one help line under the field, and the submit. Heading, AddressLabel, Help, Submit string // The sent state, which replaces the form: heading (and tab title), // body, and the link back to the form. SentHeading, SentBody, Again string // The signed-out page: the tab title's page, the heading, the body // ({app} is the app's name) and the Sign in button. SignedOutPage, SignedOutHeading, SignedOutBody, SignIn string // The sign-in email: subject ({app}) and body ({app} and {link}). EmailSubject, EmailBody string } // linkMinutes is LinkLifetime in words, so the number lives in one place. var linkMinutes = fmt.Sprintf("%d minutes", int(LinkLifetime/time.Minute)) // English is the door's copy set (shared-chrome spec §9.2). The submit // is "Sign in": Paul, 2026-10-01, ruled out "Email me a link". The hub is // never named ("Home"); the suite is Oficina (Paul, 2026-09-23). var English = DoorCopy{ Heading: "Sign in", AddressLabel: "Email address", Help: "We’ll email you a link that signs you in. It works once and expires in " + linkMinutes + ".", Submit: "Sign in", SentHeading: "Check your email", SentBody: "If that address can sign in here, we’ve sent it a link. It works once and expires in " + linkMinutes + ".", Again: "Use a different address", SignedOutPage: "Signed out", SignedOutHeading: "You’re signed out", SignedOutBody: "You’ve signed out of {app} on this device.", SignIn: "Sign in", EmailSubject: "Sign in to {app}", EmailBody: "Here’s your link to sign in to {app}:\n\n{link}\n\nIt works once and expires in " + linkMinutes + ". If you didn’t ask for it, you can ignore this email.", } // signedOutBody names the app; with none it names the suite, as the hub's // own signed-out page does. func (c DoorCopy) signedOutBody(app string) string { if app == "" { app = "Oficina" } return fill(c.SignedOutBody, app, "") } // fill puts the app's name and the link into a copy string's {app} and // {link} slots, in one pass: a name or link that itself contains a token // is not filled again. func fill(s, app, link string) string { return strings.NewReplacer("{app}", app, "{link}", link).Replace(s) } func copyOf(c *DoorCopy) DoorCopy { if c == nil { return English } return *c } // Title is a page's tab title: " · " (shared-chrome spec // §10), or whichever half is set when one is empty. func Title(page, app string) string { page, app = strings.TrimSpace(page), strings.TrimSpace(app) switch { case page == "": return app case app == "": return page } return page + " · " + app } // The form's words, read by estilo-signin. func (s SignIn) Heading() string { return copyOf(s.Copy).Heading } func (s SignIn) AddressLabel() string { return copyOf(s.Copy).AddressLabel } func (s SignIn) Help() string { return copyOf(s.Copy).Help } func (s SignIn) Submit() string { return copyOf(s.Copy).Submit } // Page is the sign-in page's part of the tab title ("Sign in"), for a // layout that appends the app's name itself; Title is the whole title. func (s SignIn) Page() string { return s.Heading() } func (s SignIn) Title() string { return Title(s.Page(), s.App) } // Sent is this door's check-your-email state: the door's heading and // body, and "Use a different address" linking to again (the form, // keeping the return; empty drops the link). func (n SignInNotice) Sent(again string) SignInNotice { c := copyOf(n.Copy) n.Heading, n.Body, n.page, n.Link = c.SentHeading, c.SentBody, c.SentHeading, nil if again != "" { n.Link = &Link{Href: again, Label: c.Again} } return n } // SignedOut is this door's signed-out page: "You’re signed out", the // sentence naming the app, and the primary Sign in linking to signin. func (n SignInNotice) SignedOut(signin string) SignInNotice { c := copyOf(n.Copy) n.Heading, n.Body, n.page, n.Link = c.SignedOutHeading, c.signedOutBody(n.App), c.SignedOutPage, nil if signin != "" { n.Link = &Link{Href: signin, Label: c.SignIn} } return n } // Page is the notice's part of the tab title: the door's own for Sent and // SignedOut ("Check your email", "Signed out"), otherwise the heading. func (n SignInNotice) Page() string { if n.page != "" { return n.page } return n.Heading } // Title is the notice's whole tab title, " · ". func (n SignInNotice) Title() string { return Title(n.Page(), n.App) } // SignInEmail is this copy set's sign-in email for app: subject and body // around link. func (c DoorCopy) SignInEmail(app, link string) (subject, body string) { return c.MailSubject(app), c.MailBody(app)(link) } // MailSubject is the sign-in email's subject, "Sign in to " in // English. (Not EmailSubject: that is the field it reads.) func (c DoorCopy) MailSubject(app string) string { return fill(c.EmailSubject, app, "") } // MailBody renders the sign-in email's body around the link. func (c DoorCopy) MailBody(app string) func(link string) string { return func(link string) string { return fill(c.EmailBody, app, link) } } // SignInEmail is the sign-in email for app in English: subject and body // around link. func SignInEmail(app, link string) (subject, body string) { return English.SignInEmail(app, link) } // EmailSubject is the sign-in email's subject in English, "Sign in to // ": what rastrillo's auth.Config.Subject takes. func EmailSubject(app string) string { return English.MailSubject(app) } // EmailBody renders the English sign-in email's body around the link: the // shape keymaildev/signin's Flow.Body and rastrillo's auth.Config.Body // take. func EmailBody(app string) func(link string) string { return English.MailBody(app) }